bugfix
This commit is contained in:
@@ -4383,97 +4383,95 @@ class DataProcessor:
|
||||
|
||||
# --- Methode für den Re-Eval Modus ---
|
||||
# Diese Methode gehört in die Klasse
|
||||
def process_reevaluation_rows(self, row_limit=None, clear_flag=True):
|
||||
def process_reevaluation_rows(self, row_limit=None, clear_flag=True,
|
||||
# NEUE PARAMETER hinzugefügt:
|
||||
process_wiki_steps=True,
|
||||
process_chatgpt_steps=True,
|
||||
process_website_steps=True):
|
||||
"""
|
||||
Verarbeitet nur Zeilen, die in Spalte A mit 'x' markiert sind.
|
||||
Ruft _process_single_row für jede dieser Zeilen auf mit force_reeval=True.
|
||||
Verarbeitet maximal row_limit Zeilen.
|
||||
Löscht optional das 'x'-Flag nach erfolgreicher Verarbeitung.
|
||||
Erlaubt die Auswahl spezifischer Verarbeitungsschritte.
|
||||
|
||||
Args:
|
||||
row_limit (int, optional): Maximale Anzahl zu verarbeitender Zeilen. Defaults to None.
|
||||
clear_flag (bool, optional): Flag 'x' nach erfolgreicher Verarbeitung löschen. Defaults to True.
|
||||
process_wiki_steps (bool, optional): Soll der Wiki-Schritt in _process_single_row ausgeführt werden?. Defaults to True.
|
||||
process_chatgpt_steps (bool, optional): Sollen ChatGPT-Schritte in _process_single_row ausgeführt werden?. Defaults to True.
|
||||
process_website_steps (bool, optional): Soll der Website-Schritt in _process_single_row ausgeführt werden?. Defaults to True.
|
||||
# Fügen Sie hier ggf. weitere Parameter hinzu, wenn Sie granularere Schritte in _process_single_row haben.
|
||||
"""
|
||||
logging.info(f"Starte Re-Evaluierungsmodus (Spalte A = 'x'). Max. Zeilen: {row_limit if row_limit is not None else 'Unbegrenzt'}")
|
||||
# Logge, welche Schritte für Re-Eval ausgewählt wurden
|
||||
selected_steps_log = []
|
||||
if process_wiki_steps: selected_steps_log.append("Wiki")
|
||||
if process_chatgpt_steps: selected_steps_log.append("ChatGPT")
|
||||
if process_website_steps: selected_steps_log.append("Website")
|
||||
logging.info(f"Ausgewählte Schritte für Re-Eval: {', '.join(selected_steps_log) if selected_steps_log else 'Keine ausgewählt!'} (force_reeval=True)")
|
||||
|
||||
|
||||
# Daten neu laden vor der Verarbeitung
|
||||
if not self.sheet_handler.load_data():
|
||||
logging.error("Fehler beim Laden der Daten für Re-Evaluation.")
|
||||
return
|
||||
|
||||
# ... (Code zum Laden der Daten, Finden der x-markierten Zeilen wie gehabt) ...
|
||||
if not self.sheet_handler.load_data(): return logging.error("Fehler beim Laden der Daten für Re-Evaluation.")
|
||||
all_data = self.sheet_handler.get_all_data_with_headers()
|
||||
if not all_data or len(all_data) <= 5:
|
||||
logging.warning("Keine Daten für Re-Evaluation gefunden.")
|
||||
return
|
||||
header_rows = 5
|
||||
# data_rows wird hier nicht direkt benötigt, wir nutzen all_data und den sheet index
|
||||
|
||||
# Annahme: COLUMN_MAP ist global verfügbar
|
||||
if not all_data or len(all_data) <= header_rows: return logging.warning("Keine Daten für Re-Evaluation gefunden.")
|
||||
reeval_col_idx = COLUMN_MAP.get("ReEval Flag")
|
||||
if reeval_col_idx is None:
|
||||
logging.error("FEHLER: 'ReEval Flag' Spaltenindex nicht in COLUMN_MAP gefunden.")
|
||||
return
|
||||
if reeval_col_idx is None: return logging.critical("FEHLER: 'ReEval Flag' Spaltenindex nicht in COLUMN_MAP gefunden.")
|
||||
|
||||
rows_to_process = []
|
||||
# Iteriere über alle Datenzeilen, um 'x' zu finden
|
||||
for idx_in_list in range(header_rows, len(all_data)):
|
||||
row_data = all_data[idx_in_list]
|
||||
row_num_in_sheet = idx_in_list + 1 # 1-basierte Zeilennummer
|
||||
row_num_in_sheet = idx_in_list + 1
|
||||
if len(row_data) > reeval_col_idx and str(row_data[reeval_col_idx]).strip().lower() == "x":
|
||||
rows_to_process.append({'row_num': row_num_in_sheet, 'data': row_data})
|
||||
|
||||
found_count = len(rows_to_process)
|
||||
logging.info(f"{found_count} Zeilen mit ReEval-Flag 'x' gefunden.")
|
||||
if found_count == 0: return logging.info("Keine Zeilen zur Re-Evaluation markiert.")
|
||||
|
||||
if found_count == 0:
|
||||
logging.info("Keine Zeilen zur Re-Evaluation markiert.")
|
||||
return
|
||||
|
||||
processed_count = 0
|
||||
updates_clear_flag = []
|
||||
rows_actually_processed = [] # Liste der Zeilennummern, die wirklich verarbeitet wurden
|
||||
rows_actually_processed = []
|
||||
|
||||
for task in rows_to_process:
|
||||
# Limit-Prüfung VOR der Verarbeitung
|
||||
if row_limit is not None and processed_count >= row_limit:
|
||||
logging.info(f"Zeilenlimit ({row_limit}) für Re-Evaluation erreicht. Breche weitere Verarbeitung ab.")
|
||||
break
|
||||
|
||||
row_num = task['row_num']
|
||||
row_data = task['data'] # Verwende die direkt geladenen Daten für die Zeile
|
||||
row_data = task['data']
|
||||
try:
|
||||
# Rufe _process_single_row mit force_reeval=True auf
|
||||
self._process_single_row(row_num, row_data,
|
||||
process_wiki=True, process_chatgpt=True, process_website=True,
|
||||
force_reeval=True) # WICHTIG: Erzwingt Verarbeitung aller Schritte!
|
||||
# RUFE _process_single_row MIT DEN NEUEN PARAMETERN AUF:
|
||||
self._process_single_row(
|
||||
row_num_in_sheet = row_num,
|
||||
row_data = row_data,
|
||||
process_wiki = process_wiki_steps, # <<< ÜBERGIBT DIE STEUERUNG
|
||||
process_chatgpt = process_chatgpt_steps, # <<< ÜBERGIBT DIE STEUERUNG
|
||||
process_website = process_website_steps, # <<< ÜBERGIBT DIE STEUERUNG
|
||||
force_reeval = True # <<< BLEIBT HIER TRUE FÜR RE-EVAL MODUS
|
||||
)
|
||||
processed_count += 1
|
||||
rows_actually_processed.append(row_num) # Füge Zeilennummer hinzu
|
||||
rows_actually_processed.append(row_num)
|
||||
|
||||
# Vorbereiten des Updates zum Löschen des 'x'-Flags
|
||||
# Vorbereiten des Updates zum Löschen des 'x'-Flags (wie gehabt)
|
||||
if clear_flag:
|
||||
flag_col_letter = self.sheet_handler._get_col_letter(reeval_col_idx + 1)
|
||||
if flag_col_letter: # Prüfe, ob der Spaltenbuchstabe ermittelt werden konnte
|
||||
updates_clear_flag.append({'range': f'{flag_col_letter}{row_num}', 'values': [['']]})
|
||||
else:
|
||||
logging.error(f"Fehler: Konnte Spaltenbuchstaben für 'ReEval Flag' nicht ermitteln.")
|
||||
|
||||
if flag_col_letter: updates_clear_flag.append({'range': f'{flag_col_letter}{row_num}', 'values': [['']]})
|
||||
else: logging.error(f"Fehler: Konnte Spaltenbuchstaben für 'ReEval Flag' ({reeval_col_idx+1}) nicht ermitteln.")
|
||||
|
||||
except Exception as e_proc:
|
||||
# Logge den spezifischen Fehler für diese Zeile
|
||||
logging.exception(f"FEHLER bei Re-Evaluation von Zeile {row_num}: {e_proc}")
|
||||
# Das 'x'-Flag wird in diesem Fall NICHT gelöscht, damit die Zeile erneut versucht werden kann.
|
||||
|
||||
# Lösche Flags am Ende in einem Batch-Update
|
||||
# Lösche Flags am Ende (wie gehabt)
|
||||
if clear_flag and updates_clear_flag:
|
||||
logging.info(f"Lösche ReEval-Flags für {len(updates_clear_flag)} erfolgreich verarbeitete Zeilen ({rows_actually_processed})...")
|
||||
# Annahme: sheet_handler.batch_update_cells existiert und nutzt logging/retry
|
||||
success = self.sheet_handler.batch_update_cells(updates_clear_flag)
|
||||
if success:
|
||||
logging.info("ReEval-Flags erfolgreich gelöscht.")
|
||||
else:
|
||||
# Fehlermeldung wird von batch_update_cells geloggt
|
||||
logging.error("FEHLER beim Löschen der ReEval-Flags.")
|
||||
|
||||
if success: logging.info("ReEval-Flags erfolgreich gelöscht.")
|
||||
else: logging.error("FEHLER beim Löschen der ReEval-Flags.")
|
||||
|
||||
logging.info(f"Re-Evaluierung abgeschlossen. {processed_count} Zeilen verarbeitet (Limit war: {row_limit}, Gefunden: {found_count}).")
|
||||
|
||||
@@ -5057,18 +5055,21 @@ class DataProcessor:
|
||||
|
||||
# ==================== MAIN FUNCTION ====================
|
||||
# ==================== MAIN FUNCTION ====================
|
||||
# Diese Funktion ist der Haupteinstiegspunkt des Skripts.
|
||||
|
||||
def main():
|
||||
# WICHTIG: Global LOG_FILE wird benötigt, aber erst nach Arg-Parsing gesetzt.
|
||||
global LOG_FILE
|
||||
|
||||
# --- Logging Setup (Konfiguration von Level und Format) ---
|
||||
# --- Initial Logging Setup (Konfiguration von Level und Format) ---
|
||||
# Diese Konfiguration wird wirksam, sobald die Handler hinzugefügt werden.
|
||||
import logging
|
||||
log_level = logging.DEBUG # Explizit DEBUG setzen für detaillierte Logs
|
||||
log_format = '%(asctime)s - %(levelname)-8s - %(name)-15s - %(message)s' # Angepasstes Format
|
||||
|
||||
# Root-Logger konfigurieren (noch ohne File Handler)
|
||||
logging.basicConfig(level=log_level, format=log_format, handlers=[]) # WICHTIG: handlers=[] verhindert default Console Handler
|
||||
# handlers=[] verhindert default Console Handler, wir fügen ihn manuell hinzu
|
||||
logging.basicConfig(level=log_level, format=log_format, handlers=[])
|
||||
|
||||
# Console Handler explizit hinzufügen
|
||||
console_handler = logging.StreamHandler()
|
||||
@@ -5082,96 +5083,53 @@ def main():
|
||||
logging.info("INFO Logging initial konfiguriert (nur Konsole).")
|
||||
|
||||
# --- Initialisierung (Argument Parser etc.) ---
|
||||
parser = argparse.ArgumentParser(description="Firmen-Datenanreicherungs-Skript v1.6.5") # Version aktualisiert
|
||||
valid_modes = ["combined", "wiki", "website", "branch", "summarize", "reeval",
|
||||
"website_lookup", "website_details", "contacts", "full_run",
|
||||
"alignment", "train_technician_model", "update_wiki",
|
||||
"find_wiki_serp"] # <-- NEUER MODUS HINZUGEFÜGT
|
||||
# Version hier (sollte mit Config.VERSION übereinstimmen)
|
||||
current_script_version = "v1.6.6" # <-- ANPASSEN, wenn Config.VERSION geändert wird
|
||||
|
||||
parser = argparse.ArgumentParser(description=f"Firmen-Datenanreicherungs-Skript {current_script_version}")
|
||||
# Liste der gültigen Modi (basierend auf Ihrer aktuellen v1.6.6 + dem neuen Modus)
|
||||
valid_modes = [
|
||||
"combined", "wiki", "website", "branch", "summarize", "reeval",
|
||||
"website_lookup", "website_details", "contacts", "full_run",
|
||||
"alignment", "train_technician_model", "update_wiki",
|
||||
"find_wiki_serp", "wiki_reextract" # <<< NEUER MODUS HIER HINZUGEFÜGT
|
||||
]
|
||||
# Stellen Sie sicher, dass diese Liste mit denelif-Zweigen unten übereinstimmt.
|
||||
|
||||
parser.add_argument("--mode", type=str, help=f"Betriebsmodus ({', '.join(valid_modes)})")
|
||||
parser.add_argument("--limit", type=int, help="Maximale Anzahl zu verarbeitender Zeilen", default=None)
|
||||
parser.add_argument("--start_row", type=int, help="Startzeile im Sheet (1-basiert) für sequenzielle Modi (full_run)", default=None) # Optionaler Startpunkt für full_run
|
||||
# start_row wird primär für full_run verwendet, kann aber generell hilfreich sein
|
||||
parser.add_argument("--start_row", type=int, help="Startzeile im Sheet (1-basiert) für sequenzielle Modi", default=None)
|
||||
|
||||
# NEUES ARGUMENT für den Re-Eval Modus zur Auswahl der Schritte
|
||||
# Standard ist "wiki,chat,web", um das bisherige Verhalten zu imitieren
|
||||
# Mögliche Werte für die Schritte: 'wiki', 'chat', 'web' (entsprechend den Parametern in _process_single_row)
|
||||
parser.add_argument("--steps", type=str, help="Komma-getrennte Liste der Schritte im 'reeval' Modus (z.B. 'wiki,chat,web'). Mögliche Schritte: wiki, chat, web.", default="wiki,chat,web")
|
||||
|
||||
# Argumente für find_wiki_serp (falls über CLI gesteuert)
|
||||
parser.add_argument("--min_umsatz", type=int, help="Mindestumsatz in Mio € für find_wiki_serp", default=200)
|
||||
parser.add_argument("--min_employees", type=int, help="Mindestmitarbeiterzahl für find_wiki_serp", default=500)
|
||||
|
||||
# Argumente für train_technician_model
|
||||
parser.add_argument("--model_out", type=str, default=MODEL_FILE, help=f"Pfad für Modell (.pkl)")
|
||||
parser.add_argument("--imputer_out", type=str, default=IMPUTER_FILE, help=f"Pfad für Imputer (.pkl)")
|
||||
parser.add_argument("--patterns_out", type=str, default=PATTERNS_FILE_TXT, help=f"Pfad für Regeln (.txt)")
|
||||
|
||||
# TODO: Fügen Sie hier weitere CLI-Argumente hinzu, falls andere Modi Parameter benötigen (z.B. für Kriterien-Modus)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
# Lade API Keys direkt am Anfang
|
||||
Config.load_api_keys() # Nutzt jetzt logging.debug/info/warning intern
|
||||
|
||||
# Betriebsmodus ermitteln
|
||||
mode = None
|
||||
if args.mode and args.mode.lower() in valid_modes:
|
||||
mode = args.mode.lower()
|
||||
# Logge erst NACHDEM FileHandler konfiguriert ist
|
||||
# logging.info(f"Betriebsmodus (aus Kommandozeile): {mode}") # Wird später geloggt
|
||||
print(f"Betriebsmodus (aus Kommandozeile): {mode}") # Frühes Feedback für User
|
||||
else: # Interaktive Abfrage
|
||||
print("\nBitte wählen Sie den Betriebsmodus:")
|
||||
print(" combined: Wiki(AX), Website(AR), Summarize(AS), Branch(AO) (Batch, Start bei leerem AO)")
|
||||
print(" wiki: Nur Wikipedia-Verifizierung (AX) (Batch, Start bei leerem AX)")
|
||||
print(" website: Nur Website-Scraping Rohtext (AR) (Batch, Start bei leerem AR)")
|
||||
print(" summarize: Nur Website-Zusammenfassung (AS) (Batch, Start bei leerem AS)")
|
||||
print(" branch: Nur Branchen-Einschätzung (AO) (Batch, Start bei leerem AO)")
|
||||
print(" update_wiki: Wiki-URL aus Spalte U nach M übernehmen & ReEval-Flag setzen")
|
||||
print(" reeval: Verarbeitet Zeilen mit 'x' in A (volle Verarbeitung)")
|
||||
print(" find_wiki_serp: Sucht fehlende Wiki-URLs (M=k.A.) für große Firmen (>500 MA) via SerpAPI") # Neuer Modus erklärt
|
||||
print(" website_lookup: Sucht fehlende Websites (D) via SerpAPI")
|
||||
# print(" website_details:Extrahiert Details für Zeilen mit 'x' (AR) - EXPERIMENTELL") # Ggf. ausblenden
|
||||
print(" contacts: Sucht LinkedIn Kontakte (AM)")
|
||||
print(" full_run: Verarbeitet sequenziell ab erster Zeile ohne AO (alle TS prüfen)")
|
||||
print(" alignment: Schreibt Header A1:AX5 (!)")
|
||||
print(" train_technician_model: Trainiert Decision Tree zur Technikerschätzung")
|
||||
|
||||
try:
|
||||
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 -> Standard: combined")
|
||||
mode = "combined"
|
||||
except Exception as e:
|
||||
print(f"Fehler Modus-Eingabe ({e}) -> Standard: combined")
|
||||
mode = "combined"
|
||||
# logging.info(f"Betriebsmodus (interaktiv gewählt): {mode}") # Wird später geloggt
|
||||
|
||||
# Zeilenlimit ermitteln
|
||||
row_limit = None
|
||||
if args.limit is not None:
|
||||
if args.limit >= 0:
|
||||
row_limit = args.limit
|
||||
# logging.info(f"Zeilenlimit (aus Kommandozeile): {row_limit}") # Wird später geloggt
|
||||
print(f"Zeilenlimit (aus Kommandozeile): {row_limit}") # Frühes Feedback
|
||||
else:
|
||||
print("Warnung: Negatives Limit ignoriert.")
|
||||
# logging.warning("Warnung: Negatives Limit ignoriert.") # Wird später geloggt
|
||||
row_limit = None
|
||||
# Frage nur bei Modi, wo es sinnvoll ist (inkl. neuer Modus)
|
||||
elif mode in ["combined", "wiki", "website", "branch", "summarize", "full_run", "reeval", "update_wiki", "find_wiki_serp"]:
|
||||
try:
|
||||
limit_input = input(f"Maximale Anzahl Zeilen für Modus '{mode}'? (Enter=alle): ")
|
||||
if limit_input.strip():
|
||||
try:
|
||||
limit_val = int(limit_input)
|
||||
if limit_val >= 0:
|
||||
row_limit = limit_val
|
||||
print(f"Zeilenlimit (interaktiv): {row_limit}")
|
||||
else:
|
||||
print("Negatives Limit -> Kein Limit")
|
||||
row_limit = None
|
||||
except ValueError:
|
||||
print("Ungültige Zahl -> Kein Limit")
|
||||
row_limit = None
|
||||
else:
|
||||
print("Kein Zeilenlimit angegeben.")
|
||||
row_limit = None
|
||||
except Exception as e:
|
||||
print(f"Fehler Limit-Eingabe ({e}) -> Kein Limit")
|
||||
row_limit = None
|
||||
# Logging der Limit-Info erfolgt nach FileHandler-Setup
|
||||
Config.load_api_keys() # Nutzt jetzt logging intern
|
||||
|
||||
# --- Logdatei-Konfiguration abschließen ---
|
||||
# Annahme: Funktion existiert und gibt Pfad zurück
|
||||
LOG_FILE = create_log_filename(mode)
|
||||
# Bestimmen Sie den Log-Modus Namen basierend auf CLI oder Interaktion
|
||||
# Wir nutzen den CLI Modus Namen, wenn er gesetzt ist, sonst einen Platzhalter.
|
||||
# Der tatsächliche Modus wird unten ermittelt und geloggt.
|
||||
log_mode_name = args.mode if args.mode else "interactive"
|
||||
LOG_FILE = create_log_filename(log_mode_name) # Annahme: create_log_filename ist global
|
||||
|
||||
try:
|
||||
file_handler = logging.FileHandler(LOG_FILE, mode='a', encoding='utf-8')
|
||||
file_handler.setLevel(log_level) # Nimm das globale Level
|
||||
@@ -5185,233 +5143,230 @@ def main():
|
||||
logging.getLogger('').handlers = [h for h in logging.getLogger('').handlers if not isinstance(h, logging.FileHandler)] # Entferne evtl. defekten Handler
|
||||
logging.error(f"Konnte FileHandler für Logdatei '{LOG_FILE}' nicht erstellen: {e}")
|
||||
|
||||
|
||||
# --- JETZT die Startmeldungen loggen (gehen jetzt in Konsole UND Datei) ---
|
||||
logging.info(f"===== Skript gestartet =====")
|
||||
logging.info(f"Version: {Config.VERSION}") # Sollte jetzt v1.6.5 sein
|
||||
logging.info(f"Betriebsmodus: {mode}")
|
||||
limit_log_text = str(row_limit) if row_limit is not None else 'N/A für diesen Modus'
|
||||
if mode in ["combined", "wiki", "website", "branch", "summarize", "full_run", "reeval", "update_wiki", "find_wiki_serp"]:
|
||||
limit_log_text = str(row_limit) if row_limit is not None else 'Unbegrenzt'
|
||||
if row_limit == 0: limit_log_text = '0 (Keine Verarbeitung geplant)'
|
||||
logging.info(f"Zeilenlimit: {limit_log_text}")
|
||||
logging.info(f"Version: {Config.VERSION}") # Sollte jetzt v1.6.6 sein
|
||||
# Der Modus wird später vom Dispatcher geloggt
|
||||
logging.info(f"Logdatei: {LOG_FILE}")
|
||||
# --- Ende finale Startinfos ---
|
||||
# Loggen Sie auch die Re-Eval Schritte, wenn das Argument gesetzt ist (unabhängig vom gewählten Modus, zur Info)
|
||||
if 'steps' in args and args.steps:
|
||||
logging.info(f"CLI Argument --steps: '{args.steps}' (relevant für 'reeval' Modus)")
|
||||
if 'min_umsatz' in args: logging.info(f"CLI Argument --min_umsatz: {args.min_umsatz}")
|
||||
if 'min_employees' in args: logging.info(f"CLI Argument --min_employees: {args.min_employees}")
|
||||
if 'model_out' in args: logging.info(f"CLI Argument --model_out: '{args.model_out}'")
|
||||
# ... loggen Sie weitere relevante CLI Argumente
|
||||
|
||||
|
||||
# --- Vorbereitung (Schema, Sheet Handler etc.) ---
|
||||
# Annahme: Diese Funktionen verwenden jetzt logging intern
|
||||
load_target_schema()
|
||||
load_target_schema() # Annahme: load_target_schema ist global definiert
|
||||
|
||||
try:
|
||||
sheet_handler = GoogleSheetHandler()
|
||||
sheet_handler = GoogleSheetHandler() # Annahme: GoogleSheetHandler ist global definiert
|
||||
except Exception as e:
|
||||
logging.critical(f"FATAL: Initialisierung des GoogleSheetHandlers fehlgeschlagen: {e}")
|
||||
logging.critical(f"Bitte Logdatei prüfen: {LOG_FILE}")
|
||||
return # Beende Skript, wenn Sheet nicht geladen werden kann
|
||||
# Annahme: DataProcessor verwendet jetzt logging intern
|
||||
data_processor = DataProcessor(sheet_handler)
|
||||
|
||||
# --- Modusausführung ---
|
||||
try:
|
||||
# Initialisiere WikipediaScraper hier, da er an DataProcessor übergeben werden muss
|
||||
wiki_scraper = WikipediaScraper() # Annahme: WikipediaScraper ist global definiert und benötigt keine Parameter oder nutzt Config
|
||||
except Exception as e:
|
||||
logging.critical(f"FATAL: Initialisierung des WikipediaScrapers fehlgeschlagen: {e}")
|
||||
logging.critical(f"Bitte Logdatei prüfen: {LOG_FILE}")
|
||||
# Das Skript kann ohne Wiki Scraper nicht sinnvoll laufen
|
||||
return
|
||||
|
||||
# Initialisiere DataProcessor Instanz mit Handlern
|
||||
# PASSEN SIE DIESEN AUFRUF AN DIE TATSÄCHLICHE __init__ SIGNATUR IHRER DataProcessor Klasse an
|
||||
# In v1.6.6 nahm sie nur sheet_handler entgegen. Für den Refactoring-Plan soll sie wiki_scraper auch nehmen.
|
||||
# Für diese Übergangsversion halten wir uns an die v1.6.6 Signatur (nur sheet_handler)
|
||||
# ABER: Methoden IN DataProcessor (wie _process_single_row) brauchen den wiki_scraper!
|
||||
# Das bedeutet, wiki_scraper muss in __init__ übergeben und als self.wiki_scraper gespeichert werden.
|
||||
# KORRIGIEREN SIE DataProcessor.__init__ ZU: def __init__(self, sheet_handler, wiki_scraper):
|
||||
data_processor = DataProcessor(sheet_handler, wiki_scraper) # <<< KORRIGIERTER AUFRUF
|
||||
|
||||
# --- Modusauswahl und Ausführung ---
|
||||
start_time = time.time()
|
||||
logging.info(f"Starte Verarbeitung um {datetime.now().strftime('%H:%M:%S')}...")
|
||||
try:
|
||||
# Batch-Modi über Dispatcher
|
||||
if mode in ["wiki", "website", "branch", "summarize", "combined"]:
|
||||
if row_limit == 0:
|
||||
logging.info("Limit 0 angegeben -> Überspringe Dispatcher für Batch-Modus.")
|
||||
else:
|
||||
# Annahme: run_dispatcher verwendet logging intern
|
||||
run_dispatcher(mode, sheet_handler, row_limit)
|
||||
|
||||
# Einzelne Zeilen Modi (kein Batch-Dispatcher)
|
||||
elif mode == "reeval":
|
||||
# Annahme: process_reevaluation_rows verwendet logging intern
|
||||
data_processor.process_reevaluation_rows(row_limit=row_limit)
|
||||
mode = None # Wird aus CLI oder Interaktion ermittelt
|
||||
|
||||
# --- Ermitteln des zu führenden Modus (CLI hat Priorität) ---
|
||||
if args.mode:
|
||||
mode = args.mode.lower()
|
||||
if mode not in valid_modes:
|
||||
logging.error(f"Ungültiger Modus '{args.mode}' über Kommandozeile angegeben. Gültige Modi: {', '.join(valid_modes)}")
|
||||
print(f"Fehler: Ungültiger Modus '{args.mode}'. Siehe --help.")
|
||||
return # Skript beenden
|
||||
logging.info(f"Betriebsmodus (CLI gewählt): {mode}")
|
||||
else:
|
||||
# --- Interaktive Modusauswahl ---
|
||||
print("\nBitte wählen Sie den Betriebsmodus:")
|
||||
# Zeigen Sie die Liste der validen Modi an
|
||||
for i, m in enumerate(valid_modes):
|
||||
print(f" {i+1}: {m}")
|
||||
|
||||
while mode is None: # Schleife, bis ein gültiger Modus gewählt wurde
|
||||
try:
|
||||
mode_input = input(f"Geben Sie den Modusnamen oder die Zahl ein: ").strip().lower()
|
||||
try:
|
||||
mode_index = int(mode_input)
|
||||
if 1 <= mode_index <= len(valid_modes): mode = valid_modes[mode_index - 1]
|
||||
else: print("Ungültige Zahl.")
|
||||
except ValueError:
|
||||
if mode_input in valid_modes: mode = mode_input
|
||||
else: print("Ungültige Eingabe.")
|
||||
|
||||
if mode: logging.info(f"Betriebsmodus (interaktiv gewählt): {mode}")
|
||||
# Wenn mode None bleibt, Schleife läuft weiter
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Fehler bei interaktiver Modus-Eingabe: {e}"); return # Skript beenden
|
||||
print(f"Fehler Modus-Eingabe ({e}).")
|
||||
|
||||
|
||||
# --- Ausführung des gewählten Modus ---
|
||||
try:
|
||||
# Rufen Sie die entsprechenden Funktionen/Methoden auf basierend auf dem gewählten 'mode'
|
||||
# Die Aufrufe hier werden auf die 'data_processor' Instanz umgestellt,
|
||||
# da die Funktionen jetzt Methoden dieser Klasse sind (oder es sein sollten).
|
||||
|
||||
if mode == "combined":
|
||||
# Der combined Mode war ein globaler run_dispatcher Aufruf.
|
||||
# run_dispatcher sollte eine Methode in DataProcessor sein.
|
||||
data_processor.run_batch_dispatcher(mode="combined", limit=args.limit) # Annahme: run_batch_dispatcher existiert in DataProcessor
|
||||
|
||||
elif mode == "wiki": # Entspricht dem Batch-Modus Wiki Verifizierung (AX)
|
||||
# process_verification_only sollte jetzt data_processor.process_verification_batch sein
|
||||
data_processor.process_verification_batch(limit=args.limit)
|
||||
|
||||
elif mode == "website": # Entspricht dem Batch-Modus Website Scraping (AT)
|
||||
# process_website_batch sollte jetzt data_processor.process_website_batch sein
|
||||
data_processor.process_website_batch(limit=args.limit)
|
||||
|
||||
elif mode == "summarize": # Entspricht dem Batch-Modus Website Summarization (AS)
|
||||
# process_website_summarization_batch sollte jetzt data_processor.process_summarization_batch sein
|
||||
data_processor.process_summarization_batch(limit=args.limit)
|
||||
|
||||
elif mode == "branch": # Entspricht dem Batch-Modus Branchen-Einstufung (AO)
|
||||
# process_branch_batch sollte jetzt data_processor.process_branch_batch sein
|
||||
data_processor.process_branch_batch(limit=args.limit)
|
||||
|
||||
elif mode == "reeval": # process_reevaluation_rows
|
||||
if args.limit is not None and args.limit <= 0:
|
||||
logging.info(f"Limit {args.limit} angegeben im Re-Eval Modus. Überspringe Verarbeitung.")
|
||||
else:
|
||||
# Parse das neue --steps Argument
|
||||
steps_list = [step.strip().lower() for step in args.steps.split(',')]
|
||||
# Mappen Sie die CLI-Schrittnamen auf die Parameter von process_reevaluation_rows
|
||||
# Die Parameter in process_reevaluation_rows (v1.6.6 Anpassung) sind:
|
||||
# process_wiki_steps, process_chatgpt_steps, process_website_steps
|
||||
process_wiki_flag = 'wiki' in steps_list
|
||||
process_chatgpt_flag = 'chat' in steps_list
|
||||
process_website_flag = 'web' in steps_list
|
||||
# Wenn Ihre process_reevaluation_rows weitere boolsche Flags akzeptiert, mappen Sie die entsprechenden CLI-Namen hier.
|
||||
|
||||
# Rufen Sie process_reevaluation_rows mit den ausgelesenen Flags auf
|
||||
# process_reevaluation_rows ist eine Methode in DataProcessor.
|
||||
data_processor.process_reevaluation_rows(
|
||||
row_limit=args.limit,
|
||||
clear_flag=True, # Standardmäßig Flag 'x' löschen
|
||||
process_wiki_steps=process_wiki_flag, # <<< ÜBERGIBT DIE STEUERUNG
|
||||
process_chatgpt_steps=process_chatgpt_flag, # <<< ÜBERGIBT DIE STEUERUNG
|
||||
process_website_steps=process_website_flag
|
||||
# Wenn Ihre process_reevaluation_rows weitere Parameter hat, übergeben Sie diese hier
|
||||
)
|
||||
|
||||
elif mode == "website_lookup":
|
||||
# Annahme: process_serp_website_lookup_for_empty verwendet logging intern
|
||||
data_processor.process_serp_website_lookup_for_empty()
|
||||
# process_serp_website_lookup_for_empty sollte jetzt data_processor.process_serp_website_lookup sein
|
||||
data_processor.process_serp_website_lookup(limit=args.limit) # Fügen Sie hier den Limit Parameter hinzu, falls gewünscht/unterstützt
|
||||
|
||||
elif mode == "website_details":
|
||||
logging.warning("Modus 'website_details' ist experimentell.")
|
||||
# Annahme: process_website_details_for_marked_rows verwendet logging intern
|
||||
data_processor.process_website_details_for_marked_rows()
|
||||
# process_website_details_for_marked_rows sollte jetzt data_processor.process_website_details sein
|
||||
data_processor.process_website_details(limit=args.limit) # Fügen Sie hier den Limit Parameter hinzu, falls gewünscht/unterstützt
|
||||
|
||||
elif mode == "contacts":
|
||||
# Annahme: process_contact_research verwendet logging intern
|
||||
process_contact_research(sheet_handler)
|
||||
# process_contact_research sollte jetzt data_processor.process_contact_research sein
|
||||
data_processor.process_contact_research(limit=args.limit) # Fügen Sie hier den Limit Parameter hinzu, falls gewünscht/unterstützt
|
||||
|
||||
elif mode == "full_run":
|
||||
if row_limit == 0:
|
||||
logging.info("Limit 0 angegeben -> Überspringe full_run.")
|
||||
else:
|
||||
# Prüfe, ob eine explizite Startzeile übergeben wurde
|
||||
start_data_index = -1 # Initialisieren
|
||||
if args.start_row and args.start_row > 5:
|
||||
header_rows = 5 # Standard-Annahme
|
||||
start_data_index = args.start_row - header_rows - 1 # Konvertiere zu 0-basiertem Datenindex
|
||||
logging.info(f"Nutze expliziten Start-Datenindex {start_data_index} (Sheet Zeile {args.start_row}) für 'full_run'.")
|
||||
else:
|
||||
logging.info("Ermittle Startindex für 'full_run' (erste Zeile ohne AO)...")
|
||||
# Annahme: get_start_row_index verwendet logging intern
|
||||
start_data_index = sheet_handler.get_start_row_index(check_column_key="Timestamp letzte Prüfung")
|
||||
elif mode == "full_run": # process_rows_sequentially
|
||||
# process_rows_sequentially ist eine Methode in DataProcessor.
|
||||
# Der Aufruf muss hier implementiert werden (Startindex Logic etc., wie im alten main Block).
|
||||
logging.warning("Modus 'full_run' benötigt noch die Implementierung des Aufrufs von process_sequential.")
|
||||
# Beispielaufruf (wenn process_sequential eine Methode ist):
|
||||
# # start_data_index logic (wie im alten main block)
|
||||
# header_rows = 5 # Annahme
|
||||
# start_data_index = 0 # Default
|
||||
# if args.start_row is not None:
|
||||
# start_data_index = args.start_row - 1 # 0-based
|
||||
# if start_data_index < header_rows: logging.warning(f"Manuelle Startzeile {args.start_row} liegt innerhalb der Header."); start_data_index = header_rows
|
||||
# else:
|
||||
# # Automatische Ermittlung der Startzeile (z.B. erste Zeile ohne AO)
|
||||
# logging.info("Automatische Ermittlung der Startzeile für sequenzielle Verarbeitung (erste Zeile ohne AO)...")
|
||||
# # get_start_row_index gibt 0-basierter Index in Daten (ohne Header) zurück
|
||||
# start_data_index_no_header = sheet_handler.get_start_row_index(check_column_key="Timestamp letzte Prüfung")
|
||||
# if start_data_index_no_header == -1: logging.error("FEHLER bei automatischer Ermittlung der Startzeile."); return
|
||||
# start_data_index = start_data_index_no_header + header_rows # 0-based index in all_data
|
||||
#
|
||||
# # Berechne num_to_process
|
||||
# if not sheet_handler.load_data(): logging.error("Fehler beim Laden der Daten."); return
|
||||
# total_rows = len(sheet_handler.get_all_data_with_headers())
|
||||
# num_available = total_rows - start_data_index # Anzahl Zeilen ab Startindex
|
||||
# num_to_process = num_available
|
||||
# if args.limit is not None and args.limit >= 0:
|
||||
# num_to_process = min(num_available, args.limit)
|
||||
#
|
||||
# if num_to_process > 0:
|
||||
# logging.info(f"'full_run': Verarbeite {num_to_process} Zeilen ab Sheet-Zeile {start_data_index + 1}.")
|
||||
# # Hier müssten Sie auch die Flags für die Schritte abfragen/übergeben
|
||||
# # Für full_run würden Sie wahrscheinlich alle Schritte wählen (oder über neues Argument steuern)
|
||||
# data_processor.process_sequential(
|
||||
# start_sheet_row = start_data_index + 1, # 1-basierte Startzeile
|
||||
# num_to_process = num_to_process,
|
||||
# process_wiki=True, # Beispiel: Alle Schritte
|
||||
# process_chatgpt=True,
|
||||
# process_website=True
|
||||
# # Wenn process_sequential granularere Flags nimmt, übergeben Sie diese hier
|
||||
# )
|
||||
# else: logging.info("Keine Zeilen für 'full_run' zu verarbeiten.")
|
||||
|
||||
# Prüfe, ob get_start_row_index einen gültigen Index zurückgab
|
||||
if start_data_index != -1:
|
||||
current_data = sheet_handler.get_data() # Hole aktuelle Daten
|
||||
if start_data_index < len(current_data):
|
||||
num_available = len(current_data) - start_data_index
|
||||
num_to_process = min(row_limit, num_available) if row_limit is not None and row_limit >= 0 else num_available
|
||||
if num_to_process > 0:
|
||||
logging.info(f"'full_run': Verarbeite {num_to_process} Zeilen ab Daten-Index {start_data_index}.")
|
||||
# Annahme: process_rows_sequentially verwendet logging intern
|
||||
data_processor.process_rows_sequentially(
|
||||
start_data_index,
|
||||
num_to_process,
|
||||
process_wiki=True,
|
||||
process_chatgpt=True,
|
||||
process_website=True
|
||||
)
|
||||
else:
|
||||
logging.info("Keine Zeilen für 'full_run' zu verarbeiten (Limit/Startindex).")
|
||||
else:
|
||||
logging.warning(f"Startindex {start_data_index} liegt hinter der letzten Datenzeile ({len(current_data)}). Keine Verarbeitung.")
|
||||
else:
|
||||
# Fehlermeldung wird von get_start_row_index erwartet
|
||||
logging.warning(f"Startindex für 'full_run' ungültig (Fehler bei Ermittlung oder Spalte nicht gefunden).")
|
||||
|
||||
elif mode == "alignment":
|
||||
print("\nACHTUNG: Dieser Modus überschreibt die Header-Zeilen A1:AX5!")
|
||||
try:
|
||||
confirm = input("Möchten Sie wirklich fortfahren? (j/N): ").strip().lower()
|
||||
except Exception as e_input:
|
||||
logging.error(f"Input-Fehler bei Bestätigung: {e_input}")
|
||||
confirm = 'n'
|
||||
if confirm == 'j':
|
||||
logging.info("Starte Alignment Demo...")
|
||||
# Annahme: alignment_demo verwendet logging intern
|
||||
alignment_demo(sheet_handler.sheet)
|
||||
else:
|
||||
logging.info("Alignment Demo abgebrochen.")
|
||||
# alignment_demo ist global und braucht sheet_handler.sheet
|
||||
alignment_demo(sheet_handler.sheet) # Stellen Sie sicher, dass alignment_demo global bleibt
|
||||
|
||||
elif mode == "train_technician_model":
|
||||
# train_technician_model sollte jetzt data_processor.train_technician_model sein
|
||||
data_processor.train_technician_model(model_out=args.model_out, imputer_out=args.imputer_out, patterns_out=args.patterns_out) # Argumente übergeben
|
||||
|
||||
elif mode == "update_wiki":
|
||||
logging.info("Starte Modus 'update_wiki'...")
|
||||
# Annahme: process_wiki_updates_from_chatgpt verwendet logging intern
|
||||
process_wiki_updates_from_chatgpt(sheet_handler, data_processor, row_limit=row_limit)
|
||||
# process_wiki_updates_from_chatgpt sollte jetzt data_processor.process_wiki_updates_from_chatgpt sein
|
||||
data_processor.process_wiki_updates_from_chatgpt(row_limit=args.limit) # row_limit Parameter hinzufügen
|
||||
|
||||
# --- NEUER MODUS ---
|
||||
elif mode == "find_wiki_serp":
|
||||
logging.info(f"Starte Modus '{mode}'...")
|
||||
min_employees_for_serp = 500 # Standardwert, ggf. über Argument steuerbar machen
|
||||
# Annahme: process_find_wiki_with_serp verwendet logging intern
|
||||
process_find_wiki_with_serp(sheet_handler, row_limit=row_limit, min_employees=min_employees_for_serp)
|
||||
# --- ENDE NEUER MODUS ---
|
||||
# process_find_wiki_with_serp sollte jetzt data_processor.process_find_wiki_serp sein
|
||||
data_processor.process_find_wiki_serp(row_limit=args.limit, min_employees=args.min_employees, min_umsatz=args.min_umsatz) # min_employees und min_umsatz hinzufügen
|
||||
|
||||
# Block für Modelltraining
|
||||
elif mode == "train_technician_model":
|
||||
logging.info(f"Starte Modus: {mode}")
|
||||
# Annahme: prepare_data_for_modeling verwendet logging intern
|
||||
prepared_df = data_processor.prepare_data_for_modeling()
|
||||
if prepared_df is not None and not prepared_df.empty:
|
||||
logging.info("Aufteilen der Daten für das Modelltraining...")
|
||||
try:
|
||||
# Definition von X und y
|
||||
X = prepared_df.drop(columns=['Techniker_Bucket', 'CRM Name', 'Anzahl_Servicetechniker_Numeric']) # CRM Name statt 'name'
|
||||
y = prepared_df['Techniker_Bucket']
|
||||
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)
|
||||
split_successful = True
|
||||
logging.info(f"Train/Test Split: {len(X_train)} Train, {len(X_test)} Test samples.")
|
||||
except KeyError as e:
|
||||
logging.error(f"FEHLER beim Train/Test Split: Spalte nicht gefunden - {e}. Stellen Sie sicher, dass prepare_data_for_modeling die Spalten korrekt zurückgibt.")
|
||||
split_successful = False
|
||||
except Exception as e:
|
||||
logging.error(f"FEHLER beim Train/Test Split: {e}")
|
||||
split_successful = False
|
||||
|
||||
if split_successful:
|
||||
logging.info("Imputation fehlender numerischer Werte (Median)...")
|
||||
numeric_features = ['Finaler_Umsatz', 'Finaler_Mitarbeiter']
|
||||
try:
|
||||
imputer = SimpleImputer(strategy='median')
|
||||
if all(nf in X_train.columns for nf in numeric_features):
|
||||
X_train[numeric_features] = imputer.fit_transform(X_train[numeric_features])
|
||||
X_test[numeric_features] = imputer.transform(X_test[numeric_features])
|
||||
imputer_filename = args.imputer_out
|
||||
with open(imputer_filename, 'wb') as f_imp: pickle.dump(imputer, f_imp)
|
||||
logging.info(f"Imputer erfolgreich trainiert und gespeichert: '{imputer_filename}'.")
|
||||
imputation_successful = True
|
||||
else:
|
||||
logging.error("FEHLER: Numerische Features für Imputation nicht in Trainingsdaten gefunden.")
|
||||
imputation_successful = False
|
||||
except Exception as e:
|
||||
logging.error(f"FEHLER bei der Imputation: {e}")
|
||||
imputation_successful = False
|
||||
elif mode == "wiki_reextract": # <<< NEUER MODUS RUFT NEUE FUNKTION AUF
|
||||
# Rufe die neu erstellte globale Funktion auf, die sheet_handler und data_processor benötigt
|
||||
# Diese Funktion implementiert die Kriterien-Logik "M gefüllt & AN leer" und ruft dann _process_single_row
|
||||
# mit den spezifischen Flags (nur Wiki) und force_reeval=True auf.
|
||||
process_wiki_reextract_missing_an(sheet_handler, data_processor, limit=args.limit) # Annahme: process_wiki_reextract_missing_an ist global definiert
|
||||
|
||||
if imputation_successful:
|
||||
logging.info("Starte Decision Tree Training mit GridSearchCV...")
|
||||
param_grid = {
|
||||
'criterion': ['gini', 'entropy'], 'max_depth': [6, 8, 10, 12, 15],
|
||||
'min_samples_split': [20, 40, 60], 'min_samples_leaf': [10, 20, 30],
|
||||
'ccp_alpha': [0.0, 0.001, 0.005]
|
||||
}
|
||||
dtree = DecisionTreeClassifier(random_state=42, class_weight='balanced')
|
||||
grid_search = GridSearchCV(estimator=dtree, param_grid=param_grid, cv=5,
|
||||
scoring='f1_weighted', n_jobs=-1, verbose=1)
|
||||
try:
|
||||
grid_search.fit(X_train, y_train)
|
||||
best_estimator = grid_search.best_estimator_
|
||||
logging.info(f"GridSearchCV abgeschlossen.")
|
||||
logging.info(f"Beste Parameter: {grid_search.best_params_}")
|
||||
logging.info(f"Bester F1-Score (gewichtet, CV): {grid_search.best_score_:.4f}")
|
||||
|
||||
model_filename = args.model_out
|
||||
with open(model_filename, 'wb') as f_mod: pickle.dump(best_estimator, f_mod)
|
||||
logging.info(f"Bestes Modell gespeichert: '{model_filename}'.")
|
||||
training_successful = True
|
||||
except Exception as e_train:
|
||||
logging.exception(f"FEHLER während des Trainings: {e_train}")
|
||||
training_successful = False
|
||||
|
||||
if training_successful:
|
||||
logging.info("Evaluiere Modell auf dem Test-Set...")
|
||||
try:
|
||||
y_pred = best_estimator.predict(X_test)
|
||||
test_accuracy = accuracy_score(y_test, y_pred)
|
||||
# Sicherstellen, dass Klassen als Liste von Strings übergeben werden
|
||||
class_labels = [str(cls) for cls in best_estimator.classes_]
|
||||
report = classification_report(y_test, y_pred, zero_division=0,
|
||||
labels=best_estimator.classes_, target_names=class_labels)
|
||||
conf_matrix = confusion_matrix(y_test, y_pred, labels=best_estimator.classes_)
|
||||
conf_matrix_df = pd.DataFrame(conf_matrix, index=class_labels, columns=class_labels)
|
||||
|
||||
logging.info(f"\n--- Evaluation Test-Set ---")
|
||||
logging.info(f"Genauigkeit: {test_accuracy:.4f}")
|
||||
logging.info(f"Classification Report:\n{report}")
|
||||
logging.info(f"Confusion Matrix:\n{conf_matrix_df}")
|
||||
print(f"\nModell Genauigkeit (Test): {test_accuracy:.4f}")
|
||||
|
||||
logging.info("Extrahiere Baumregeln...")
|
||||
try:
|
||||
feature_names = list(X_train.columns)
|
||||
rules_text = export_text(best_estimator, feature_names=feature_names,
|
||||
show_weights=True, spacing=3)
|
||||
patterns_filename = args.patterns_out
|
||||
with open(patterns_filename, 'w', encoding='utf-8') as f_rules:
|
||||
f_rules.write(rules_text)
|
||||
logging.info(f"Regeln als Text gespeichert: '{patterns_filename}'.")
|
||||
except Exception as e_export:
|
||||
logging.error(f"Fehler beim Exportieren der Regeln: {e_export}")
|
||||
except Exception as e_eval:
|
||||
logging.exception(f"Fehler bei der Evaluation des Test-Sets: {e_eval}")
|
||||
else:
|
||||
logging.warning("Datenvorbereitung für Modelltraining fehlgeschlagen oder ergab keine Daten.")
|
||||
|
||||
else:
|
||||
logging.error(f"Unbekannter Modus '{mode}' wurde zur Ausführung übergeben.")
|
||||
# Dies sollte nicht passieren, wenn die Validierung oben korrekt ist
|
||||
logging.error(f"Unerwarteter Modus '{mode}' erreicht das Ausführungsende.")
|
||||
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logging.warning("Skript durch Benutzer unterbrochen (KeyboardInterrupt).")
|
||||
print("\n! Skript wurde manuell beendet.")
|
||||
except Exception as e:
|
||||
logging.critical(f"FATAL: Unerwarteter Fehler im Haupt-Ausführungsblock des Modus '{mode}': {e}")
|
||||
# Dieser Block fängt Fehler ab, die in den aufgerufenen Funktionen/Methoden passieren
|
||||
logging.critical(f"FATAL: Unerwarteter Fehler während der Ausführung von Modus '{mode}': {e}")
|
||||
logging.exception("Traceback des kritischen Fehlers:")
|
||||
|
||||
# --- Abschluss ---
|
||||
@@ -5424,48 +5379,30 @@ def main():
|
||||
# Schließe Logging Handler explizit
|
||||
logging.shutdown()
|
||||
|
||||
# Logfile Pfad für den Nutzer ausgeben
|
||||
print(f"\nVerarbeitung abgeschlossen. Logfile: {LOG_FILE}")
|
||||
|
||||
|
||||
# Führt die main-Funktion aus, wenn das Skript direkt gestartet wird
|
||||
if __name__ == '__main__':
|
||||
# --- WICHTIG: Fehlende Imports hier hinzufügen ---
|
||||
import functools # Für retry decorator nötig
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from sklearn.model_selection import train_test_split, GridSearchCV
|
||||
from sklearn.impute import SimpleImputer
|
||||
from sklearn.tree import DecisionTreeClassifier, export_text
|
||||
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
|
||||
import json
|
||||
import pickle
|
||||
import concurrent.futures
|
||||
import threading
|
||||
# --- Ende fehlende Imports ---
|
||||
# --- Sicherstellen, dass alle globalen Imports hier sind ---
|
||||
# ... (alle Imports wie am Anfang des Skripts) ...
|
||||
|
||||
# --- Annahme: Decorator Definition ist hier oder importiert ---
|
||||
# Beispielhafte Decorator Definition (falls nicht in separater Datei)
|
||||
def retry_on_failure(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
# ... (Implementierung des Decorators wie zuvor) ...
|
||||
# Minimalistische Version zur Kompilierung:
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except Exception as e:
|
||||
logging.warning(f"Retry wird übersprungen (Dummy-Decorator): Fehler in {func.__name__}: {e}")
|
||||
raise e # Fehler weitergeben
|
||||
return wrapper
|
||||
# --- Ende Decorator Annahme ---
|
||||
# --- Sicherstellen, dass alle globalen Helfer-Funktionen hier oder importiert sind ---
|
||||
# ... (Alle Ihre globalen Helfer-Funktionen: clean_text, normalize_company_name,
|
||||
# extract_numeric_value, get_numeric_filter_value, call_openai_chat, serp_wikipedia_lookup,
|
||||
# serp_website_lookup, search_linkedin_contacts, get_gender, get_email_address,
|
||||
# fuzzy_similarity, is_valid_wikipedia_article_url, evaluate_branche_chatgpt,
|
||||
# summarize_website_content, load_target_schema, map_external_branch, alignment_demo,
|
||||
# retry_on_failure, create_log_filename, debug_print, _process_batch (falls global)) ...
|
||||
|
||||
# --- Annahme: Restliche Funktionen/Klassen sind definiert ---
|
||||
# z.B. Config, COLUMN_MAP, create_log_filename, load_target_schema,
|
||||
# GoogleSheetHandler, WikipediaScraper, DataProcessor,
|
||||
# Helper-Funktionen (simple_normalize_url etc.),
|
||||
# Batch-Funktionen (run_dispatcher etc.),
|
||||
# API-Funktionen (call_openai_chat etc.),
|
||||
# Neue Funktionen (serp_wikipedia_lookup, process_find_wiki_with_serp)
|
||||
# ...
|
||||
# --- Ende Funktions/Klassen Annahmen ---
|
||||
# NEU: Die Kriterien-Funktion und die Funktion, die den neuen Modus steuert, müssen hier global sein
|
||||
# Kopieren Sie die Definitionen von criteria_m_filled_an_empty und process_wiki_reextract_missing_an hierher.
|
||||
|
||||
# --- Sicherstellen, dass alle Klassen hier definiert sind ---
|
||||
# ... (Config, GoogleSheetHandler, WikipediaScraper) ...
|
||||
# KORRIGIERTE DataProcessor Klasse Definition (mit __init__(self, sheet_handler, wiki_scraper))
|
||||
# und allen Methoden, die Sie bis jetzt hatten, IN DER KLASSE eingerückt.
|
||||
|
||||
# Die main Funktion aufrufen
|
||||
main()
|
||||
Reference in New Issue
Block a user