bugfix
This commit is contained in:
@@ -69,7 +69,7 @@ PATTERNS_FILE_JSON = "technician_patterns.json" # Optional
|
||||
# ==================== KONFIGURATION ====================
|
||||
class Config:
|
||||
# ... (Alle deine bisherigen Config-Einstellungen) ...
|
||||
VERSION = "v1.6.4" # Versionsnummer erhöhen
|
||||
VERSION = "v1.6.5" # Versionsnummer erhöhen
|
||||
LANG = "de"
|
||||
SHEET_URL = "https://docs.google.com/spreadsheets/d/1u_gHr9JUfmV1-iviRzbSe3575QEp7KLhK5jFV_gJcgo"
|
||||
MAX_RETRIES = 3
|
||||
@@ -3814,67 +3814,85 @@ class DataProcessor:
|
||||
|
||||
|
||||
def process_reevaluation_rows(self, row_limit=None, clear_flag=True):
|
||||
"""
|
||||
Verarbeitet nur Zeilen, die in Spalte A mit 'x' markiert sind.
|
||||
Ruft _process_single_row für jede dieser Zeilen auf.
|
||||
Verarbeitet maximal row_limit Zeilen.
|
||||
Löscht optional das 'x'-Flag nach erfolgreicher Verarbeitung.
|
||||
"""
|
||||
debug_print(f"Starte Re-Evaluierungsmodus (Spalte A = 'x'). Max. Zeilen: {row_limit if row_limit is not None else 'Unbegrenzt'}")
|
||||
"""
|
||||
Verarbeitet nur Zeilen, die in Spalte A mit 'x' markiert sind.
|
||||
Ruft _process_single_row für jede dieser Zeilen auf.
|
||||
Verarbeitet maximal row_limit Zeilen.
|
||||
Löscht optional das 'x'-Flag nach erfolgreicher Verarbeitung.
|
||||
"""
|
||||
logging.info(f"Starte Re-Evaluierungsmodus (Spalte A = 'x'). Max. Zeilen: {row_limit if row_limit is not None else 'Unbegrenzt'}")
|
||||
|
||||
# Lade Daten frisch, um aktuelle Flags zu sehen
|
||||
if not self.sheet_handler.load_data(): return
|
||||
all_data = self.sheet_handler.get_all_data_with_headers()
|
||||
if not all_data or len(all_data) <= 5: return
|
||||
header_rows = 5
|
||||
data_rows = all_data[header_rows:]
|
||||
# Lade Daten frisch
|
||||
if not self.sheet_handler.load_data(): return
|
||||
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 = all_data[header_rows:]
|
||||
|
||||
reeval_col_idx = COLUMN_MAP.get("ReEval Flag")
|
||||
if reeval_col_idx is None: return debug_print("FEHLER: 'ReEval Flag' nicht in COLUMN_MAP.")
|
||||
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
|
||||
|
||||
rows_to_process = []
|
||||
# Finde zuerst alle Kandidaten
|
||||
for idx, row in enumerate(data_rows):
|
||||
if len(row) > reeval_col_idx and row[reeval_col_idx].strip().lower() == "x":
|
||||
row_num_in_sheet = idx + header_rows + 1
|
||||
rows_to_process.append({'row_num': row_num_in_sheet, 'data': row})
|
||||
# Finde zuerst alle Kandidaten (Zeilennummer im Sheet und Rohdaten)
|
||||
rows_to_process = []
|
||||
for idx, row in enumerate(data_rows):
|
||||
if len(row) > reeval_col_idx and row[reeval_col_idx].strip().lower() == "x":
|
||||
row_num_in_sheet = idx + header_rows + 1
|
||||
rows_to_process.append({'row_num': row_num_in_sheet, 'data': row})
|
||||
|
||||
debug_print(f"{len(rows_to_process)} Zeilen mit ReEval-Flag 'x' gefunden.")
|
||||
found_count = len(rows_to_process)
|
||||
logging.info(f"{found_count} Zeilen mit ReEval-Flag 'x' gefunden.")
|
||||
|
||||
processed_count = 0
|
||||
updates_clear_flag = []
|
||||
if found_count == 0:
|
||||
logging.info("Keine Zeilen zur Re-Evaluation markiert.")
|
||||
return
|
||||
|
||||
# Verarbeite die gefundenen Kandidaten bis zum Limit
|
||||
for task in rows_to_process:
|
||||
if row_limit is not None and processed_count >= row_limit:
|
||||
debug_print(f"Zeilenlimit ({row_limit}) erreicht. Breche Re-Evaluation ab.")
|
||||
break
|
||||
processed_count = 0
|
||||
updates_clear_flag = []
|
||||
rows_actually_processed = [] # Liste der Zeilennummern, die verarbeitet wurden
|
||||
|
||||
row_num = task['row_num']
|
||||
row_data = task['data']
|
||||
debug_print(f"--- Re-Evaluiere Zeile {row_num} ---")
|
||||
try:
|
||||
# Führe volle Verarbeitung für diese Zeile durch
|
||||
# _process_single_row prüft intern Timestamps AN, AT, AO
|
||||
self._process_single_row(row_num, row_data, process_wiki=True, process_chatgpt=True, process_website=True)
|
||||
processed_count += 1
|
||||
# Verarbeite die gefundenen Kandidaten bis zum Limit
|
||||
# WICHTIG: Iteriere nur über die Kandidatenliste, nicht die gesamten Daten
|
||||
for task in rows_to_process:
|
||||
# --- KORRIGIERTE LIMIT-PRÜFUNG ---
|
||||
# Prüfe das Limit *bevor* die Verarbeitung beginnt
|
||||
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 # Verlasse die Schleife
|
||||
|
||||
# Optional: Flag nach Verarbeitung löschen
|
||||
if clear_flag:
|
||||
flag_col_letter = self.sheet_handler._get_col_letter(reeval_col_idx + 1)
|
||||
updates_clear_flag.append({'range': f'{flag_col_letter}{row_num}', 'values': [['']]})
|
||||
row_num = task['row_num']
|
||||
row_data = task['data']
|
||||
logging.info(f"--- Re-Evaluiere Zeile {row_num} ---")
|
||||
try:
|
||||
# Führe volle Verarbeitung für diese Zeile durch
|
||||
# _process_single_row prüft intern Timestamps AN, AT, AO etc.
|
||||
self._process_single_row(row_num, row_data, process_wiki=True, process_chatgpt=True, process_website=True)
|
||||
processed_count += 1
|
||||
rows_actually_processed.append(row_num) # Füge zur Liste der verarbeiteten hinzu
|
||||
|
||||
except Exception as e_proc:
|
||||
debug_print(f"FEHLER bei Re-Evaluation von Zeile {row_num}: {e_proc}")
|
||||
# Flag hier nicht löschen, damit es beim nächsten Mal versucht wird
|
||||
# Optional: Flag nach *erfolgreicher* Verarbeitung löschen (Sammeln)
|
||||
if clear_flag:
|
||||
flag_col_letter = self.sheet_handler._get_col_letter(reeval_col_idx + 1)
|
||||
if flag_col_letter: # Nur wenn Buchstabe gültig
|
||||
updates_clear_flag.append({'range': f'{flag_col_letter}{row_num}', 'values': [['']]})
|
||||
|
||||
# Lösche Flags am Ende gebündelt
|
||||
if clear_flag and updates_clear_flag:
|
||||
debug_print(f"Lösche ReEval-Flags für {len(updates_clear_flag)} verarbeitete Zeilen...")
|
||||
success = self.sheet_handler.batch_update_cells(updates_clear_flag)
|
||||
if not success: debug_print("FEHLER beim Löschen der ReEval-Flags.")
|
||||
except Exception as e_proc:
|
||||
# Logge Fehler, aber mache mit der nächsten Zeile weiter
|
||||
logging.exception(f"FEHLER bei Re-Evaluation von Zeile {row_num}: {e_proc}")
|
||||
# Flag hier nicht löschen, damit es beim nächsten Mal versucht wird
|
||||
|
||||
debug_print(f"Re-Evaluierung abgeschlossen. {processed_count} Zeilen verarbeitet (Limit: {row_limit}).")
|
||||
# Lösche Flags am Ende gebündelt für die *erfolgreich* verarbeiteten Zeilen
|
||||
if clear_flag and updates_clear_flag:
|
||||
# Logge, welche Flags gelöscht werden
|
||||
logging.info(f"Lösche ReEval-Flags für {len(updates_clear_flag)} erfolgreich verarbeitete Zeilen ({rows_actually_processed})...")
|
||||
success = self.sheet_handler.batch_update_cells(updates_clear_flag)
|
||||
if not success:
|
||||
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}).")
|
||||
|
||||
|
||||
def process_website_details_for_marked_rows(self):
|
||||
@@ -4088,214 +4106,346 @@ class DataProcessor:
|
||||
return None
|
||||
|
||||
|
||||
# ==================== MAIN FUNCTION ====================
|
||||
# ==================== MAIN FUNCTION ====================
|
||||
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) ---
|
||||
# Diese Konfiguration wird wirksam, sobald die Handler hinzugefügt werden.
|
||||
import logging
|
||||
# Ganz am Anfang von main, vor dem ersten debug_print
|
||||
log_level = logging.DEBUG # Explizit setzen
|
||||
logging.basicConfig(level=log_level,
|
||||
format='%(asctime)s - %(levelname)s - %(name)s - %(message)s', # Name des Loggers hinzufügen
|
||||
filename=LOG_FILE, # In Datei schreiben
|
||||
filemode='a') # Anfügen
|
||||
# Optional: Auch auf Konsole ausgeben
|
||||
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
|
||||
|
||||
# Console Handler explizit hinzufügen
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(log_level)
|
||||
console_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s'))
|
||||
logging.getLogger('').addHandler(console_handler)
|
||||
console_handler.setLevel(log_level) # Nimm das globale Level
|
||||
console_handler.setFormatter(logging.Formatter(log_format))
|
||||
logging.getLogger('').addHandler(console_handler) # Füge zum Root-Logger hinzu
|
||||
# --- Ende Initial Logging Setup ---
|
||||
|
||||
# Testnachricht nach Logging-Setup
|
||||
logging.debug("DEBUG Logging ist konfiguriert.")
|
||||
logging.info("INFO Logging ist konfiguriert.")
|
||||
# Testnachricht (geht nur an Konsole, da File Handler noch fehlt)
|
||||
logging.debug("DEBUG Logging initial konfiguriert (nur Konsole).")
|
||||
logging.info("INFO Logging initial konfiguriert (nur Konsole).")
|
||||
|
||||
# --- Initialisierung ---
|
||||
parser = argparse.ArgumentParser(description="Firmen-Datenanreicherungs-Skript")
|
||||
# NEU: 'update_wiki' hinzugefügt
|
||||
# --- 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"]
|
||||
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", default=None) # Optionaler Startpunkt
|
||||
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)")
|
||||
args = parser.parse_args()
|
||||
|
||||
Config.load_api_keys()
|
||||
# Lade API Keys direkt am Anfang
|
||||
Config.load_api_keys() # Nutzt jetzt logging.debug intern
|
||||
|
||||
# Betriebsmodus ermitteln
|
||||
mode = None
|
||||
if args.mode and args.mode.lower() in valid_modes: mode = args.mode.lower(); print(f"Betriebsmodus (aus Kommandozeile): {mode}")
|
||||
if args.mode and args.mode.lower() in valid_modes:
|
||||
mode = args.mode.lower()
|
||||
logging.info(f"Betriebsmodus (aus Kommandozeile): {mode}")
|
||||
else: # Interaktive Abfrage
|
||||
print("Bitte wählen Sie den Betriebsmodus:")
|
||||
print("\nBitte wählen Sie den Betriebsmodus:")
|
||||
# ... (print-Anweisungen für Modi bleiben gleich) ...
|
||||
print(" combined: Wiki(AX), Website-Scrape(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 T übernehmen & Reparse/Re-Branch") # NEU
|
||||
print(" reeval: Verarbeitet Zeilen mit 'x' (volle Verarbeitung, alle TS prüfen)")
|
||||
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(" website_lookup: Sucht fehlende Websites (D)")
|
||||
print(" website_details:Extrahiert Details für Zeilen mit 'x' (AR)")
|
||||
print(" website_details:Extrahiert Details für Zeilen mit 'x' (AR) - EXPERIMENTELL") # Ggf. anpassen
|
||||
print(" contacts: Sucht LinkedIn Kontakte (AM)")
|
||||
print(" full_run: Verarbeitet sequentiell 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 -> combined"); mode = "combined"
|
||||
except Exception as e: print(f"Fehler Modus-Eingabe ({e}) -> combined"); mode = "combined"
|
||||
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}")
|
||||
|
||||
# Zeilenlimit ermitteln
|
||||
row_limit = None
|
||||
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 Limit ignoriert."); row_limit = None
|
||||
elif mode in ["combined", "wiki", "website", "branch", "summarize", "full_run"]: # Nur für relevante Modi fragen
|
||||
try:
|
||||
limit_input = input("Max Zeilen? (Enter=alle): ");
|
||||
if limit_input.strip():
|
||||
try:
|
||||
limit_val = int(limit_input)
|
||||
if limit_val >= 0: row_limit = limit_val; print(f"Zeilenlimit: {row_limit}")
|
||||
else: print("Negatives Limit -> Kein Limit"); row_limit = None
|
||||
except ValueError: print("Ungültige Zahl -> Kein Limit"); row_limit = None
|
||||
else: row_limit = None; print("Kein Zeilenlimit.")
|
||||
except Exception as e: print(f"Fehler Limit-Eingabe ({e}) -> Kein Limit"); row_limit = None
|
||||
if args.limit >= 0:
|
||||
row_limit = args.limit
|
||||
logging.info(f"Zeilenlimit (aus Kommandozeile): {row_limit}")
|
||||
else:
|
||||
logging.warning("Warnung: Negatives Limit ignoriert.")
|
||||
row_limit = None
|
||||
elif mode in ["combined", "wiki", "website", "branch", "summarize", "full_run", "reeval"]: # Relevante Modi
|
||||
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
|
||||
logging.info(f"Zeilenlimit (interaktiv): {row_limit}")
|
||||
else:
|
||||
logging.warning("Negatives Limit -> Kein Limit")
|
||||
row_limit = None
|
||||
except ValueError:
|
||||
logging.warning("Ungültige Zahl -> Kein Limit")
|
||||
row_limit = None
|
||||
else:
|
||||
logging.info("Kein Zeilenlimit angegeben.")
|
||||
row_limit = None
|
||||
except Exception as e:
|
||||
logging.error(f"Fehler Limit-Eingabe ({e}) -> Kein Limit")
|
||||
row_limit = None
|
||||
|
||||
# --- Logdatei-Konfiguration abschließen ---
|
||||
LOG_FILE = create_log_filename(mode) # Annahme: Funktion existiert und gibt Pfad zurück
|
||||
try:
|
||||
file_handler = logging.FileHandler(LOG_FILE, mode='a', encoding='utf-8')
|
||||
file_handler.setLevel(log_level) # Nimm das globale Level
|
||||
file_handler.setFormatter(logging.Formatter(log_format))
|
||||
logging.getLogger('').addHandler(file_handler) # Füge zum Root-Logger hinzu
|
||||
logging.info(f"Logging wird jetzt auch in Datei geschrieben: {LOG_FILE}")
|
||||
except Exception as e:
|
||||
logging.error(f"Konnte FileHandler für Logdatei '{LOG_FILE}' nicht erstellen: {e}")
|
||||
# Programm kann weiterlaufen, loggt aber nur auf Konsole
|
||||
# --- Ende Logdatei-Konfiguration ---
|
||||
|
||||
# Logfile initialisieren
|
||||
LOG_FILE = create_log_filename(mode)
|
||||
debug_print(f"===== Skript gestartet ====="); debug_print(f"Version: {Config.VERSION}")
|
||||
debug_print(f"Betriebsmodus: {mode}");
|
||||
# --- Logge finale Startinfos (jetzt auch in 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"]:
|
||||
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)'
|
||||
debug_print(f"Zeilenlimit: {limit_log_text}")
|
||||
debug_print(f"Logdatei: {LOG_FILE}")
|
||||
if mode in ["combined", "wiki", "website", "branch", "summarize", "full_run", "reeval"]:
|
||||
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"Logdatei: {LOG_FILE}")
|
||||
# --- Ende finale Startinfos ---
|
||||
|
||||
# --- Vorbereitung ---
|
||||
load_target_schema()
|
||||
try: sheet_handler = GoogleSheetHandler();
|
||||
except Exception as e: debug_print(f"FATAL: Init GSheet: {e}"); print(f"FEHLER GSheet. Log: {LOG_FILE}"); return
|
||||
data_processor = DataProcessor(sheet_handler)
|
||||
# --- Vorbereitung (Sheet Handler etc.) ---
|
||||
load_target_schema() # Nutzt jetzt logging intern
|
||||
try:
|
||||
sheet_handler = GoogleSheetHandler() # Nutzt jetzt logging intern
|
||||
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
|
||||
data_processor = DataProcessor(sheet_handler) # Nutzt jetzt logging intern
|
||||
|
||||
# --- Modusausführung ---
|
||||
start_time = time.time()
|
||||
debug_print(f"Starte Verarbeitung um {datetime.now().strftime('%H:%M:%S')}...")
|
||||
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: debug_print("Limit 0 -> Skip Dispatcher.")
|
||||
else: run_dispatcher(mode, sheet_handler, row_limit)
|
||||
if row_limit == 0:
|
||||
logging.info("Limit 0 angegeben -> Überspringe Dispatcher für Batch-Modus.")
|
||||
else:
|
||||
run_dispatcher(mode, sheet_handler, row_limit) # Nutzt jetzt logging intern
|
||||
|
||||
# Einzelne Zeilen Modi (kein Batch-Dispatcher)
|
||||
elif mode == "reeval": data_processor.process_reevaluation_rows()
|
||||
elif mode == "website_lookup": data_processor.process_serp_website_lookup_for_empty()
|
||||
elif mode == "website_details": data_processor.process_website_details_for_marked_rows()
|
||||
elif mode == "contacts": process_contact_research(sheet_handler)
|
||||
elif mode == "reeval":
|
||||
data_processor.process_reevaluation_rows(row_limit=row_limit) # Nutzt jetzt logging intern
|
||||
|
||||
elif mode == "website_lookup":
|
||||
data_processor.process_serp_website_lookup_for_empty() # Nutzt jetzt logging intern
|
||||
|
||||
elif mode == "website_details":
|
||||
logging.warning("Modus 'website_details' ist experimentell.")
|
||||
data_processor.process_website_details_for_marked_rows() # Nutzt jetzt logging intern
|
||||
|
||||
elif mode == "contacts":
|
||||
process_contact_research(sheet_handler) # Nutzt jetzt logging intern
|
||||
|
||||
elif mode == "full_run":
|
||||
if row_limit == 0: debug_print("Limit 0 -> Skip full_run.")
|
||||
else:
|
||||
start_index = sheet_handler.get_start_row_index(check_column_key="Timestamp letzte Prüfung")
|
||||
if start_index != -1 and start_index < len(sheet_handler.get_data()):
|
||||
num_available = len(sheet_handler.get_data()) - start_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:
|
||||
# Übergebe Flags an process_rows_sequentially
|
||||
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.")
|
||||
else: debug_print(f"Startindex {start_index} für 'full_run' ungültig.")
|
||||
if row_limit == 0:
|
||||
logging.info("Limit 0 angegeben -> Überspringe full_run.")
|
||||
else:
|
||||
# Prüfe, ob eine explizite Startzeile übergeben wurde
|
||||
if args.start_row and args.start_row > 5:
|
||||
start_data_index = args.start_row - 5 - 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)...")
|
||||
start_data_index = sheet_handler.get_start_row_index(check_column_key="Timestamp letzte Prüfung")
|
||||
|
||||
if start_data_index != -1 and start_data_index < len(sheet_handler.get_data()):
|
||||
num_available = len(sheet_handler.get_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}.")
|
||||
# Übergebe Flags an process_rows_sequentially
|
||||
data_processor.process_rows_sequentially(
|
||||
start_data_index,
|
||||
num_to_process,
|
||||
process_wiki=True,
|
||||
process_chatgpt=True,
|
||||
process_website=True
|
||||
) # Nutzt jetzt logging intern
|
||||
else:
|
||||
logging.info("Keine Zeilen für 'full_run' zu verarbeiten (Limit/Startindex).")
|
||||
else:
|
||||
logging.warning(f"Startindex {start_data_index} für 'full_run' ungültig oder keine Zeilen mehr.")
|
||||
|
||||
elif mode == "alignment":
|
||||
print("\nACHTUNG: Überschreibt A1:AX5!"); # AX statt AS
|
||||
try: confirm = input("Fortfahren? (j/N): ").strip().lower()
|
||||
except Exception as e_input: print(f"Input-Fehler: {e_input}"); confirm = 'n'
|
||||
if confirm == 'j': alignment_demo(sheet_handler.sheet)
|
||||
else: print("Abgebrochen.")
|
||||
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...")
|
||||
alignment_demo(sheet_handler.sheet) # Annahme: nutzt logging intern
|
||||
else:
|
||||
logging.info("Alignment Demo abgebrochen.")
|
||||
|
||||
# --- NEU: Wiki Update Modus ---
|
||||
# --- Wiki Update Modus ---
|
||||
elif mode == "update_wiki":
|
||||
process_wiki_updates_from_chatgpt(sheet_handler, data_processor)
|
||||
# --- Ende Wiki Update Modus ---
|
||||
logging.info("Starte Modus 'update_wiki'...")
|
||||
process_wiki_updates_from_chatgpt(sheet_handler, data_processor, row_limit=row_limit) # Limit optional übergeben
|
||||
|
||||
# Block für Modelltraining (wie von dir bereitgestellt)
|
||||
# Block für Modelltraining
|
||||
elif mode == "train_technician_model":
|
||||
debug_print(f"Starte Modus: {mode}")
|
||||
logging.info(f"Starte Modus: {mode}")
|
||||
# (Dieser Block verwendet jetzt logging intern, keine Anpassungen hier nötig,
|
||||
# außer sicherzustellen, dass prepare_data_for_modeling logging nutzt)
|
||||
prepared_df = data_processor.prepare_data_for_modeling()
|
||||
if prepared_df is not None and not prepared_df.empty:
|
||||
debug_print("Aufteilen der Daten...")
|
||||
logging.info("Aufteilen der Daten für das Modelltraining...")
|
||||
try:
|
||||
# Definition von X und y (wie im Original, Annahme: prepare_data... hat Spalten 'name' etc.)
|
||||
X = prepared_df.drop(columns=['Techniker_Bucket', 'name', 'Anzahl_Servicetechniker_Numeric'])
|
||||
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
|
||||
except Exception as e: debug_print(f"FEHLER Split: {e}"); split_successful = False
|
||||
logging.info(f"Train/Test Split: {len(X_train)} Train, {len(X_test)} Test samples.")
|
||||
except Exception as e:
|
||||
logging.error(f"FEHLER beim Train/Test Split: {e}")
|
||||
split_successful = False
|
||||
|
||||
if split_successful:
|
||||
debug_print("Imputation...")
|
||||
logging.info("Imputation fehlender numerischer Werte (Median)...")
|
||||
numeric_features = ['Finaler_Umsatz', 'Finaler_Mitarbeiter']
|
||||
try:
|
||||
imputer = SimpleImputer(strategy='median')
|
||||
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; pickle.dump(imputer, open(imputer_filename, 'wb'))
|
||||
debug_print(f"Imputer gespeichert: '{imputer_filename}'.")
|
||||
imputation_successful = True
|
||||
except Exception as e: debug_print(f"FEHLER Imputation: {e}"); imputation_successful = False
|
||||
# Stelle sicher, dass Spalten existieren bevor Transformation
|
||||
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
|
||||
|
||||
if imputation_successful:
|
||||
debug_print("Starte Training/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]}
|
||||
logging.info("Starte Decision Tree Training mit GridSearchCV...")
|
||||
# Parameter Grid (wie im Original)
|
||||
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] # Cost-Complexity Pruning
|
||||
}
|
||||
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)
|
||||
# GridSearchCV (wie im Original)
|
||||
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_
|
||||
debug_print(f"GridSearchCV fertig. Beste Params: {grid_search.best_params_}, Score: {grid_search.best_score_:.4f}")
|
||||
model_filename = args.model_out; pickle.dump(best_estimator, open(model_filename, 'wb'))
|
||||
debug_print(f"Modell gespeichert: '{model_filename}'.")
|
||||
training_successful = True
|
||||
except Exception as e_train: debug_print(f"FEHLER Training: {e_train}"); training_successful = False; import traceback; debug_print(traceback.format_exc())
|
||||
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}")
|
||||
|
||||
# Speichere das beste Modell
|
||||
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.error(f"FEHLER während des Trainings: {e_train}")
|
||||
logging.exception("Traceback Training:") # Loggt Traceback
|
||||
training_successful = False
|
||||
|
||||
if training_successful:
|
||||
debug_print("Evaluiere Test-Set..."); y_pred = best_estimator.predict(X_test)
|
||||
test_accuracy = accuracy_score(y_test, y_pred)
|
||||
report = classification_report(y_test, y_pred, zero_division=0, labels=best_estimator.classes_, target_names=best_estimator.classes_)
|
||||
conf_matrix = confusion_matrix(y_test, y_pred, labels=best_estimator.classes_)
|
||||
conf_matrix_df = pd.DataFrame(conf_matrix, index=best_estimator.classes_, columns=best_estimator.classes_)
|
||||
debug_print(f"\n--- Evaluation Test-Set ---\nGenauigkeit: {test_accuracy:.4f}\nBericht:\n{report}\nMatrix:\n{conf_matrix_df}"); print(f"\nModell Genauigkeit (Test): {test_accuracy:.4f}")
|
||||
debug_print("\nExtrahiere Regeln...");
|
||||
logging.info("Evaluiere Modell auf dem Test-Set...")
|
||||
try:
|
||||
feature_names = list(X_train.columns); class_names = best_estimator.classes_
|
||||
rules_text = export_text(best_estimator, feature_names=feature_names, class_names=class_names, show_weights=True, spacing=3)
|
||||
patterns_filename = args.patterns_out;
|
||||
with open(patterns_filename, 'w', encoding='utf-8') as f: f.write(rules_text)
|
||||
debug_print(f"Regeln gespeichert: '{patterns_filename}'.")
|
||||
except Exception as e_export: debug_print(f"Fehler Export Regeln: {e_export}")
|
||||
else: debug_print("Datenvorbereitung fehlgeschlagen -> Abbruch ML Training.")
|
||||
y_pred = best_estimator.predict(X_test)
|
||||
test_accuracy = accuracy_score(y_test, y_pred)
|
||||
report = classification_report(y_test, y_pred, zero_division=0,
|
||||
labels=best_estimator.classes_, target_names=best_estimator.classes_)
|
||||
conf_matrix = confusion_matrix(y_test, y_pred, labels=best_estimator.classes_)
|
||||
conf_matrix_df = pd.DataFrame(conf_matrix, index=best_estimator.classes_, columns=best_estimator.classes_)
|
||||
|
||||
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}") # Auch auf Konsole
|
||||
|
||||
logging.info("Extrahiere Baumregeln...")
|
||||
try:
|
||||
feature_names = list(X_train.columns)
|
||||
class_names = list(best_estimator.classes_) # Sicherstellen, dass es eine Liste ist
|
||||
rules_text = export_text(best_estimator, feature_names=feature_names,
|
||||
show_weights=True, spacing=3) # class_names optional
|
||||
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.error(f"Fehler bei der Evaluation des Test-Sets: {e_eval}")
|
||||
else:
|
||||
logging.warning("Datenvorbereitung für Modelltraining fehlgeschlagen oder ergab keine Daten.")
|
||||
|
||||
else:
|
||||
debug_print(f"Unbekannter Modus '{mode}'.")
|
||||
logging.error(f"Unbekannter Modus '{mode}' wurde zur Ausführung übergeben.")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logging.warning("Skript durch Benutzer unterbrochen (KeyboardInterrupt).")
|
||||
print("\n! Skript wurde manuell beendet.")
|
||||
except Exception as e:
|
||||
debug_print(f"FATAL: Unerwarteter Fehler in main try-Block: {e}")
|
||||
import traceback; debug_print(traceback.format_exc())
|
||||
# Fange alle unerwarteten Fehler im Hauptblock ab
|
||||
logging.critical(f"FATAL: Unerwarteter Fehler im Haupt-Ausführungsblock des Modus '{mode}': {e}")
|
||||
logging.exception("Traceback des kritischen Fehlers:") # Loggt den Traceback
|
||||
|
||||
# --- Abschluss ---
|
||||
end_time = time.time(); duration = end_time - start_time
|
||||
debug_print(f"Verarbeitung abgeschlossen um {datetime.now().strftime('%H:%M:%S')}.")
|
||||
debug_print(f"Gesamtdauer: {duration:.2f} Sekunden.")
|
||||
debug_print(f"===== Skript beendet =====")
|
||||
if LOG_FILE:
|
||||
try:
|
||||
# 'with' startet in der nächsten Zeile
|
||||
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 Exception as e:
|
||||
# Optional: Warnung ausgeben, wenn das finale Schreiben fehlschlägt
|
||||
print(f"[WARNUNG] Konnte letzte Log-Nachricht nicht schreiben: {e}")
|
||||
pass # Programm soll trotzdem normal beenden
|
||||
# --- ENDE KORRIGIERTER BLOCK ---
|
||||
end_time = time.time()
|
||||
duration = end_time - start_time
|
||||
logging.info(f"Verarbeitung abgeschlossen um {datetime.now().strftime('%H:%M:%S')}.")
|
||||
logging.info(f"Gesamtdauer: {duration:.2f} Sekunden.")
|
||||
logging.info(f"===== Skript beendet =====")
|
||||
|
||||
print(f"Verarbeitung abgeschlossen. Logfile: {LOG_FILE}")
|
||||
# Schließe Logging Handler explizit (optional, aber sauber)
|
||||
logging.shutdown()
|
||||
|
||||
print(f"\nVerarbeitung abgeschlossen. Logfile: {LOG_FILE}")
|
||||
|
||||
|
||||
# Führt die main-Funktion aus, wenn das Skript direkt gestartet wird
|
||||
|
||||
Reference in New Issue
Block a user