diff --git a/brancheneinstufung.py b/brancheneinstufung.py index f73e24fb..45e9f679 100644 --- a/brancheneinstufung.py +++ b/brancheneinstufung.py @@ -578,126 +578,72 @@ def normalize_company_name(name): def process_wiki_updates_from_chatgpt(sheet_handler, data_processor): """ Identifiziert Zeilen, bei denen Wiki-Konsistenz (S) NICHT 'OK'/'X (Updated)' ist - und ein alternativer Artikel in T vorgeschlagen wurde (URL, != M). + und ein alternativer Artikel in U vorgeschlagen wurde (URL, != M). Kopiert neue URL nach M, führt Reparse (N-R) & Re-Branch (W-Y) durch, löscht Timestamps (AN, AX, AO, AP) und markiert S als 'X (Updated)'. """ debug_print("Starte Modus: Wiki-Updates basierend auf ChatGPT-Vorschlägen...") - if not sheet_handler.load_data(): - debug_print("FEHLER: Konnte Sheet-Daten nicht laden für Wiki-Updates.") - return + if not sheet_handler.load_data(): return all_data = sheet_handler.get_all_data_with_headers() - if not all_data or len(all_data) <= 5: - debug_print("FEHLER/WARNUNG: Keine Daten zum Verarbeiten in Wiki-Updates gefunden.") - return + if not all_data or len(all_data) <= 5: return header_rows = 5 data_rows = all_data[header_rows:] - # --- Indizes holen --- - required_keys = [ - "Chat Wiki Konsistenzprüfung", "Chat Vorschlag Wiki Artikel", "Wiki URL", - "Wiki Absatz", "Wiki Branche", "Wiki Umsatz", "Wiki Mitarbeiter", "Wiki Kategorien", - "Chat Vorschlag Branche", "Chat Konsistenz Branche", "Chat Begründung Abweichung Branche", - "Wikipedia Timestamp", "Wiki Verif. Timestamp", "Timestamp letzte Prüfung", "Version", - "Website Zusammenfassung", "CRM Branche", "CRM Beschreibung" - ] - col_indices = {} - all_keys_found = True - # Überprüfe jeden Schlüssel und logge genau, welcher fehlt + # Indizes holen + required_keys = ["Chat Wiki Konsistenzprüfung", "Chat Vorschlag Wiki Artikel", "Wiki URL", "Wiki Absatz", "Wiki Branche", "Wiki Umsatz", "Wiki Mitarbeiter", "Wiki Kategorien", "Chat Vorschlag Branche", "Chat Konsistenz Branche", "Chat Begründung Abweichung Branche", "Wikipedia Timestamp", "Wiki Verif. Timestamp", "Timestamp letzte Prüfung", "Version", "Website Zusammenfassung", "CRM Branche", "CRM Beschreibung"] + col_indices = {}; all_keys_found = True for key in required_keys: idx = COLUMN_MAP.get(key) - if idx is None: - debug_print(f"FEHLER: Schlüssel '{key}' für Spaltenindex fehlt in COLUMN_MAP!") - all_keys_found = False - col_indices[key] = idx # Speichere auch None, um Fehler unten zu vermeiden + if idx is None: debug_print(f"FEHLER: Schlüssel '{key}' fehlt!"); all_keys_found = False + col_indices[key] = idx + if not all_keys_found: return debug_print("Breche Wiki-Updates ab.") - if not all_keys_found: - debug_print("Breche Wiki-Updates ab, da Spaltenindizes fehlen.") - return - - # --- Variablen für Verarbeitung --- all_sheet_updates = [] processed_rows_count = 0 - skipped_rows_count = 0 error_rows_count = 0 - wiki_scraper = data_processor.wiki_scraper # Nutze Instanz + wiki_scraper = data_processor.wiki_scraper - # --- Hauptschleife über Datenzeilen --- for idx, row in enumerate(data_rows): - row_num_in_sheet = idx + header_rows + 1 # 1-basierte Zeilennummer + row_num_in_sheet = idx + header_rows + 1 - # --- Werte sicher lesen --- - # Funktion zum sicheren Holen von Werten aus der Zeile - def get_value(key): + def get_value(key): # Sicherer Zugriff auf Spaltenindex index = col_indices.get(key) - if index is not None and len(row) > index: - return row[index] - return "" # Leerer String als Standard + if index is not None and len(row) > index: return row[index] + return "" konsistenz_s = get_value("Chat Wiki Konsistenzprüfung") - vorschlag_t = get_value("Chat Vorschlag Wiki Artikel") + vorschlag_u = get_value("Chat Vorschlag Wiki Artikel") # Liest Spalte U url_m = get_value("Wiki URL") - # --- Bedingung prüfen --- - is_update_candidate = False - new_url = "" + # Bedingung prüfen + is_update_candidate = False; new_url = "" konsistenz_s_upper = konsistenz_s.strip().upper() - vorschlag_t_cleaned = vorschlag_t.strip() + vorschlag_u_cleaned = vorschlag_u.strip() url_m_cleaned = url_m.strip() - - # Bedingung 1: Status in S ist nicht OK und nicht bereits als "Updated" markiert condition1_status_nok = konsistenz_s_upper not in ["OK", "X (UPDATED)", ""] - - # Bedingung 2: Vorschlag T ist eine valide URL - condition2_t_is_url = vorschlag_t_cleaned.lower().startswith(("http://", "https://")) - - # Bedingung 3: Vorgeschlagene URL T unterscheidet sich von aktueller URL M - condition3_t_differs_m = False - if condition2_t_is_url: # Prüfe nur, wenn T eine URL ist - new_url = vorschlag_t_cleaned # Setze new_url hier - if new_url != url_m_cleaned: - condition3_t_differs_m = True - - # Finale Entscheidung - is_update_candidate = condition1_status_nok and condition2_t_is_url and condition3_t_differs_m + condition2_u_is_url = vorschlag_u_cleaned.lower().startswith(("http://", "https://")) + condition3_u_differs_m = False + if condition2_u_is_url: new_url = vorschlag_u_cleaned; condition3_u_differs_m = new_url != url_m_cleaned + is_update_candidate = condition1_status_nok and condition2_u_is_url and condition3_u_differs_m # Detailliertes Debugging für relevante Zeilen - if row_num_in_sheet in [28, 40, 42, 388, 416, 478, 523, 527, 545, 571, 630] or idx < 5: - debug_print(f"\n--- DEBUG Zeile {row_num_in_sheet} ---") - debug_print(f" S='{konsistenz_s}' -> Status NOK? {condition1_status_nok} (Wert: '{konsistenz_s_upper}')") - debug_print(f" T='{vorschlag_t}' -> Ist URL? {condition2_t_is_url} (Wert: '{vorschlag_t_cleaned}')") - debug_print(f" M='{url_m}' -> (Wert Cleaned: '{url_m_cleaned}')") - debug_print(f" Bedingung (T != M)?: {condition3_t_differs_m}") - debug_print(f" => Update-Kandidat? {is_update_candidate}") - debug_print(f"--- ENDE DEBUG Zeile {row_num_in_sheet} ---\n") + if row_num_in_sheet in [28, 40, 42, 388, 416, 478, 523, 527, 545, 571, 630] or idx < 2: + debug_print(f"\n--- DEBUG Zeile {row_num_in_sheet} ---"); debug_print(f" S='{konsistenz_s}' -> NOK? {condition1_status_nok}"); debug_print(f" U='{vorschlag_u}' -> URL? {condition2_u_is_url}"); debug_print(f" M='{url_m}'"); debug_print(f" U!=M? {condition3_u_differs_m}"); debug_print(f" => Update? {is_update_candidate}"); debug_print(f"--- ENDE DEBUG ---\n") - # --- Verarbeitung des Kandidaten --- if is_update_candidate: debug_print(f"Zeile {row_num_in_sheet}: Verarbeite Update-Kandidat. Neue URL: {new_url}") try: - # --- Wiki Reparse --- - debug_print(f" -> Reparsing Wiki-Daten für {new_url}...") - new_wiki_data = wiki_scraper.extract_company_data(new_url) - time.sleep(0.2) # Kleinere Pause nach Scrape - - # --- Branch Neuberechnung --- - crm_branche = get_value("CRM Branche") - crm_beschreibung = get_value("CRM Beschreibung") - website_summary = get_value("Website Zusammenfassung") - debug_print(f" -> Neuberechnung der Branche...") - new_branch_result = evaluate_branche_chatgpt( - crm_branche, crm_beschreibung, - new_wiki_data.get('branche', 'k.A.'), - new_wiki_data.get('categories', 'k.A.'), - website_summary - ) - time.sleep(0.2) # Kleine Pause nach ChatGPT - - # --- Updates sammeln --- - # Spaltenbuchstaben (vereinfacht für Lesbarkeit) - m_l=sheet_handler._get_col_letter(col_indices["Wiki URL"]+1); n_l=sheet_handler._get_col_letter(col_indices["Wiki Absatz"]+1); o_l=sheet_handler._get_col_letter(col_indices["Wiki Branche"]+1); p_l=sheet_handler._get_col_letter(col_indices["Wiki Umsatz"]+1); q_l=sheet_handler._get_col_letter(col_indices["Wiki Mitarbeiter"]+1); r_l=sheet_handler._get_col_letter(col_indices["Wiki Kategorien"]+1); w_l=sheet_handler._get_col_letter(col_indices["Chat Vorschlag Branche"]+1); x_l=sheet_handler._get_col_letter(col_indices["Chat Konsistenz Branche"]+1); y_l=sheet_handler._get_col_letter(col_indices["Chat Begründung Abweichung Branche"]+1); an_l=sheet_handler._get_col_letter(col_indices["Wikipedia Timestamp"]+1); ax_l=sheet_handler._get_col_letter(col_indices["Wiki Verif. Timestamp"]+1); ao_l=sheet_handler._get_col_letter(col_indices["Timestamp letzte Prüfung"]+1); ap_l=sheet_handler._get_col_letter(col_indices["Version"]+1); t_l=sheet_handler._get_col_letter(col_indices["Chat Vorschlag Wiki Artikel"]+1); s_l=sheet_handler._get_col_letter(col_indices["Chat Wiki Konsistenzprüfung"]+1) + # Wiki Reparse + debug_print(f" -> Reparsing Wiki: {new_url}...") + new_wiki_data = wiki_scraper.extract_company_data(new_url); time.sleep(0.2) + # Branch Neuberechnung + crm_branche = get_value("CRM Branche"); crm_beschreibung = get_value("CRM Beschreibung"); website_summary = get_value("Website Zusammenfassung") + debug_print(f" -> Neuberechnung Branch...") + new_branch_result = evaluate_branche_chatgpt(crm_branche, crm_beschreibung, new_wiki_data.get('branche', 'k.A.'), new_wiki_data.get('categories', 'k.A.'), website_summary); time.sleep(0.2) + # Updates sammeln + m_l=sheet_handler._get_col_letter(col_indices["Wiki URL"]+1); n_l=sheet_handler._get_col_letter(col_indices["Wiki Absatz"]+1); o_l=sheet_handler._get_col_letter(col_indices["Wiki Branche"]+1); p_l=sheet_handler._get_col_letter(col_indices["Wiki Umsatz"]+1); q_l=sheet_handler._get_col_letter(col_indices["Wiki Mitarbeiter"]+1); r_l=sheet_handler._get_col_letter(col_indices["Wiki Kategorien"]+1); w_l=sheet_handler._get_col_letter(col_indices["Chat Vorschlag Branche"]+1); x_l=sheet_handler._get_col_letter(col_indices["Chat Konsistenz Branche"]+1); y_l=sheet_handler._get_col_letter(col_indices["Chat Begründung Abweichung Branche"]+1); an_l=sheet_handler._get_col_letter(col_indices["Wikipedia Timestamp"]+1); ax_l=sheet_handler._get_col_letter(col_indices["Wiki Verif. Timestamp"]+1); ao_l=sheet_handler._get_col_letter(col_indices["Timestamp letzte Prüfung"]+1); ap_l=sheet_handler._get_col_letter(col_indices["Version"]+1); vorschlag_u_letter=sheet_handler._get_col_letter(col_indices["Chat Vorschlag Wiki Artikel"]+1); konsistenz_s_letter=sheet_handler._get_col_letter(col_indices["Chat Wiki Konsistenzprüfung"]+1) row_updates = [ {'range': f'{m_l}{row_num_in_sheet}', 'values': [[new_url]]}, {'range': f'{n_l}{row_num_in_sheet}', 'values': [[new_wiki_data.get('first_paragraph', 'k.A.')]]}, @@ -708,33 +654,43 @@ def process_wiki_updates_from_chatgpt(sheet_handler, data_processor): {'range': f'{w_l}{row_num_in_sheet}', 'values': [[new_branch_result.get("branch", "Fehler")]]}, {'range': f'{x_l}{row_num_in_sheet}', 'values': [[new_branch_result.get("consistency", "Fehler")]]}, {'range': f'{y_l}{row_num_in_sheet}', 'values': [[new_branch_result.get("justification", "Fehler")]]}, - {'range': f'{an_l}{row_num_in_sheet}', 'values': [[""]]}, # Timestamps leeren - {'range': f'{ax_l}{row_num_in_sheet}', 'values': [[""]]}, - {'range': f'{ao_l}{row_num_in_sheet}', 'values': [[""]]}, - {'range': f'{ap_l}{row_num_in_sheet}', 'values': [[""]]}, # Version leeren - {'range': f'{t_l}{row_num_in_sheet}', 'values': [["Korrektur übernommen"]]}, - {'range': f'{s_l}{row_num_in_sheet}', 'values': [["X (Updated)"]]}, # Status aktualisieren + {'range': f'{an_l}{row_num_in_sheet}', 'values': [[""]]}, {'range': f'{ax_l}{row_num_in_sheet}', 'values': [[""]]}, + {'range': f'{ao_l}{row_num_in_sheet}', 'values': [[""]]}, {'range': f'{ap_l}{row_num_in_sheet}', 'values': [[""]]}, + {'range': f'{vorschlag_u_letter}{row_num_in_sheet}', 'values': [["Korrektur übernommen"]]}, + {'range': f'{konsistenz_s_letter}{row_num_in_sheet}', 'values': [["X (Updated)"]]}, ] all_sheet_updates.extend(row_updates) - processed_rows_count += 1 # Erfolgreich verarbeitet + processed_rows_count += 1 debug_print(f" -> Updates für Zeile {row_num_in_sheet} vorbereitet.") + # Keine Pause mehr hier pro Zeile, nur am Ende des Batches except Exception as e_row: error_rows_count += 1 - debug_print(f"FEHLER bei Verarbeitung von Update-Kandidat Zeile {row_num_in_sheet}: {e_row}") - # Optional Traceback loggen: - # import traceback; debug_print(traceback.format_exc()) + debug_print(f"FEHLER Verarbeitung Zeile {row_num_in_sheet}: {e_row}") + # import traceback; debug_print(traceback.format_exc()) # Bei Bedarf Traceback loggen # --- Batch Update am Ende --- if all_sheet_updates: - debug_print(f"Sende Batch-Update für {processed_rows_count} korrigierte Wiki-Einträge ({len(all_sheet_updates)} Zellen)...") - success = sheet_handler.batch_update_cells(all_sheet_updates) - if success: debug_print(f"Sheet-Update für Wiki-Korrekturen erfolgreich.") - else: debug_print(f"FEHLER beim Sheet-Update für Wiki-Korrekturen.") - else: - debug_print("Keine Zeilen gefunden, die eine Wiki-URL-Korrektur benötigen/erfüllen.") + debug_print(f"BEREIT ZUM SENDEN: Batch-Update für {processed_rows_count} korrigierte Wiki-Einträge ({len(all_sheet_updates)} Zellen)...") + # Optional: Logge Beispiel-Updates + if len(all_sheet_updates) > 0: debug_print(f" -> Beispiel Update 1: {all_sheet_updates[0]}") + if len(all_sheet_updates) > 15: debug_print(f" -> Beispiel Update 15: {all_sheet_updates[14]}") - debug_print(f"Wiki-Updates basierend auf ChatGPT abgeschlossen. {processed_rows_count} Zeilen aktualisiert, {error_rows_count} Fehler bei Verarbeitung.") + success = False + try: + debug_print(" -> Rufe sheet_handler.batch_update_cells AUF...") + success = sheet_handler.batch_update_cells(all_sheet_updates) + debug_print(f" -> Aufruf von sheet_handler.batch_update_cells BEENDET. Erfolg? {success}") + except Exception as e_update: + debug_print(f" -> FEHLER direkt beim Aufruf von batch_update_cells in process_wiki_updates: {e_update}") + import traceback; debug_print(traceback.format_exc()) + + if success: debug_print(f"Sheet-Update Wiki-Korrekturen laut Rückgabewert erfolgreich.") + else: debug_print(f"FEHLER/Kein Erfolg Sheet-Update Wiki-Korrekturen.") + else: + debug_print("Keine Zeilen für Wiki-URL-Korrektur gefunden/verarbeitet.") + + debug_print(f"Wiki-Updates basierend auf ChatGPT abgeschlossen. {processed_rows_count} Zeilen verarbeitet, {error_rows_count} Fehler bei Verarbeitung.") def extract_numeric_value(raw_value, is_umsatz=False): """Extrahiert und normalisiert Zahlenwerte (Umsatz in Mio, Mitarbeiter).""" @@ -1136,203 +1092,123 @@ def token_count(text): class GoogleSheetHandler: def __init__(self): - """Initialisiert den Handler, verbindet und lädt initiale Daten.""" self.sheet = None self.sheet_values = [] - self.headers = [] # Speichert die erste Zeile als Header-Namen + self.headers = [] try: self._connect() if self.sheet: - self.load_data() # Erste Datenladung bei Initialisierung + self.load_data() except Exception as e: debug_print(f"FATAL: Fehler bei Initialisierung von GoogleSheetHandler: {e}") - # Wirft einen Fehler, damit das Hauptprogramm weiß, dass es nicht weitergehen kann raise ConnectionError(f"Google Sheet Handler Init failed: {e}") - # retry_on_failure Decorator sollte hier angewendet werden @retry_on_failure def _connect(self): - """Stellt Verbindung zum Google Sheet her.""" - self.sheet = None # Sicherstellen, dass sheet vor try None ist + self.sheet = None debug_print("Verbinde mit Google Sheets...") try: scope = ["https://www.googleapis.com/auth/spreadsheets"] creds = ServiceAccountCredentials.from_json_keyfile_name(CREDENTIALS_FILE, scope) gc = gspread.authorize(creds) sh = gc.open_by_url(Config.SHEET_URL) - self.sheet = sh.sheet1 # Greift auf das erste Blatt zu + self.sheet = sh.sheet1 debug_print("Verbindung zu Google Sheets erfolgreich.") except gspread.exceptions.APIError as e: - # Logge spezifische API-Fehler von Google debug_print(f"FEHLER bei Google API Verbindung: Status {e.response.status_code} - {e.response.text[:200]}") - raise e # Fehler weitergeben, damit retry greift + raise e except Exception as e: - # Logge andere Verbindungsfehler debug_print(f"FEHLER bei der Google Sheets Verbindung: {type(e).__name__} - {e}") - raise e # Fehler weitergeben + raise e - # retry_on_failure Decorator sollte hier angewendet werden @retry_on_failure def load_data(self): - """Lädt alle Daten aus dem Sheet und aktualisiert self.sheet_values und self.headers.""" if not self.sheet: debug_print("Fehler: Keine Sheet-Verbindung zum Laden der Daten.") self.sheet_values = [] self.headers = [] - return False # Signalisiert Fehler + return False debug_print("Lade Daten aus Google Sheet...") try: - self.sheet_values = self.sheet.get_all_values() # Daten neu holen + self.sheet_values = self.sheet.get_all_values() if not self.sheet_values: debug_print("Warnung: Google Sheet scheint leer zu sein oder keine Daten zurückgegeben.") self.headers = [] - elif len(self.sheet_values) >= 1: - self.headers = self.sheet_values[0] # Speichere die erste Zeile als Header - else: - self.headers = [] # Sollte nicht passieren, wenn sheet_values nicht leer war - + return True + if len(self.sheet_values) >= 1: self.headers = self.sheet_values[0] + else: self.headers = [] debug_print(f"Daten neu geladen: {len(self.sheet_values)} Zeilen insgesamt.") - return True # Signalisiert Erfolg + return True except gspread.exceptions.APIError as e: debug_print(f"Google API Fehler beim Laden der Sheet Daten: Status {e.response.status_code} - {e.response.text[:200]}") - raise e # Damit retry greift + raise e except Exception as e: debug_print(f"Allgemeiner Fehler beim Laden der Google Sheet Daten: {e}") - raise e # Damit retry greift + raise e def get_data(self): - """Gibt die aktuell im Handler gespeicherten Daten zurück (ohne die ersten 5 Header-Zeilen).""" - header_rows = 5 # Definiert die Anzahl der zu überspringenden Header-Zeilen + header_rows = 5 if not self.sheet_values or len(self.sheet_values) <= header_rows: - if self.sheet_values: # Logge nur, wenn Daten da, aber zu wenige + if self.sheet_values: debug_print(f"Warnung in get_data: Nur {len(self.sheet_values)} Zeilen vorhanden, weniger als {header_rows} Header-Zeilen erwartet.") return [] - # Gibt eine Slice der Liste zurück, die die Datenzeilen enthält return self.sheet_values[header_rows:] def get_all_data_with_headers(self): - """Gibt alle aktuell im Handler gespeicherten Daten inklusive Header zurück.""" if not self.sheet_values: debug_print("Warnung in get_all_data_with_headers: Keine Daten im Handler gespeichert.") - # Optional: Versuche neu zu laden? Oder einfach leere Liste zurückgeben? - # self.load_data() # Erneuter Ladeversuch - # return self.sheet_values - return [] # Gib leere Liste zurück, wenn nichts geladen ist + return [] return self.sheet_values def _get_col_letter(self, col_idx_1_based): - """ Konvertiert 1-basierten Spaltenindex in Buchstaben (A, B, ..., Z, AA, ...). """ - string = "" - n = col_idx_1_based - if n < 1: return None # Ungültiger Index - while n > 0: - n, remainder = divmod(n - 1, 26) - string = chr(65 + remainder) + string + string = ""; n = col_idx_1_based + if n < 1: return None + while n > 0: n, remainder = divmod(n - 1, 26); string = chr(65 + remainder) + string return string - # --- ANGEPASST: Sucht jetzt nur noch nach EXAKT LEER ("") --- def get_start_row_index(self, check_column_key, min_sheet_row=7): - """ - Findet den Index der ersten Zeile (0-basiert für Daten nach Header), - ab einer Mindestzeilennummer im Sheet, in der der Wert in der - Spalte (definiert durch check_column_key) EXAKT LEER ("") ist. - Lädt die Daten vor der Prüfung neu. - - Args: - check_column_key (str): Der Schlüssel in COLUMN_MAP für die zu prüfende Spalte. - min_sheet_row (int): Die 1-basierte Zeilennummer im Sheet, ab der gesucht werden soll. - - Returns: - int: Der 0-basierte Index in der Datenliste (ohne Header), - oder -1 bei Fehler (z.B. Schlüssel nicht gefunden), - oder der Index nach der letzten Datenzeile, wenn alle gefüllt sind. - """ - # Lade Daten *vor* der Prüfung neu, um Aktualität sicherzustellen - if not self.load_data(): - debug_print("FEHLER beim Laden der Daten in get_start_row_index. Breche ab.") - return -1 # Fehlerindikator - + if not self.load_data(): return -1 header_rows = 5 - data_rows = self.get_data() # Greift auf die neu geladenen Daten zu + data_rows = self.get_data() + if not data_rows: return 0 - if not data_rows: - debug_print("Keine Datenzeilen vorhanden für get_start_row_index nach Neuladen.") - return 0 # Index 0 signalisiert Start am Anfang (oder keine Daten) - - # Hole den Spaltenindex aus COLUMN_MAP check_column_index = COLUMN_MAP.get(check_column_key) if check_column_index is None: debug_print(f"FEHLER: Schlüssel '{check_column_key}' nicht in COLUMN_MAP gefunden!") - return -1 # Fehlerindikator + return -1 actual_col_letter = self._get_col_letter(check_column_index + 1) - # Berechne den 0-basierten Startindex für die *Datenliste* data_rows search_start_index_in_data = max(0, min_sheet_row - header_rows - 1) - - debug_print(f"get_start_row_index: Suche ab Daten-Index {search_start_index_in_data} (Sheet-Zeile {search_start_index_in_data + header_rows + 1}) nach EXAKT LEEREM Wert (=='') in Spalte '{check_column_key}' ({actual_col_letter})...") + debug_print(f"get_start_row_index: Suche ab Daten-Index {search_start_index_in_data} nach EXAKT LEEREM Wert (=='') in Spalte '{check_column_key}' ({actual_col_letter})...") if search_start_index_in_data >= len(data_rows): - debug_print(f"Start-Suchindex ({search_start_index_in_data}) >= Datenlänge ({len(data_rows)}). Alle vorherigen Zeilen scheinen gefüllt.") - return len(data_rows) # Index nach der letzten Zeile + debug_print(f"Start-Suchindex ({search_start_index_in_data}) >= Datenlänge ({len(data_rows)}).") + return len(data_rows) - # Durchlaufe die Datenzeilen ab dem berechneten Startindex for i in range(search_start_index_in_data, len(data_rows)): row = data_rows[i] current_sheet_row = i + header_rows + 1 - - cell_value = "" # Standardwert, falls Spalte nicht existiert - is_exactly_empty = False + cell_value = ""; is_exactly_empty = True if len(row) > check_column_index: - cell_value = row[check_column_index] # Hole den Rohwert - # Prüfe, ob der Wert EXAKT ein leerer String ist - if cell_value == "": - is_exactly_empty = True - else: - # Spalte existiert nicht -> gilt als leer - is_exactly_empty = True - - # Logge nur relevante Prüfungen - log_debug = (i == search_start_index_in_data or i % 1000 == 0 or is_exactly_empty or i in range(10110, 10116)) # Logge um Zeile 10113 - if log_debug: - debug_print(f" -> Prüfe Daten-Index {i} (Sheet {current_sheet_row}): Wert in {actual_col_letter}='{cell_value}' (Typ: {type(cell_value)}). Ist exakt leer ('')? {is_exactly_empty}") - + cell_value = row[check_column_index] + if cell_value != "": is_exactly_empty = False + log_debug = (i == search_start_index_in_data or i % 1000 == 0 or is_exactly_empty or i in range(10110, 10116)) + if log_debug: debug_print(f" -> Prüfe Daten-Index {i} (Sheet {current_sheet_row}): Wert in {actual_col_letter}='{cell_value}' (Typ: {type(cell_value)}). Ist exakt leer ('')? {is_exactly_empty}") if is_exactly_empty: debug_print(f"Erste Zeile ab {min_sheet_row} mit EXAKT LEEREM Wert in Spalte {actual_col_letter} gefunden: Zeile {current_sheet_row} (Daten-Index {i})") - return i # Gibt den 0-basierten Index *innerhalb der Datenliste* zurück + return i - # Wenn die Schleife durchläuft, sind alle Zeilen ab dem Start gefüllt last_index = len(data_rows) debug_print(f"Alle Zeilen ab Daten-Index {search_start_index_in_data} haben einen nicht-leeren Wert in Spalte {actual_col_letter}. Nächster Daten-Index wäre {last_index}.") return last_index - # retry_on_failure Decorator sollte hier angewendet werden @retry_on_failure def batch_update_cells(self, update_data): - """ - Führt ein Batch-Update im Google Sheet durch. Beinhaltet Fehlerbehandlung. - - Args: - update_data (list): Eine Liste von Dictionaries, jedes mit 'range' und 'values'. - z.B. [{'range': 'A1', 'values': [['Wert']]}, ...] - - Returns: - bool: True bei Erfolg, False bei Fehler nach Retries. - """ - if not self.sheet: - debug_print("FEHLER: Keine Sheet-Verbindung für Batch-Update.") - return False - if not update_data: - return True # Kein Fehler, aber nichts zu tun - - try: - self.sheet.batch_update(update_data, value_input_option='USER_ENTERED') - return True - except gspread.exceptions.APIError as e: - debug_print(f"Google API Fehler beim Batch-Update: Status {e.response.status_code} - {e.response.text[:500]}") - raise e # Fehler weitergeben, damit der Decorator ihn fängt - except Exception as e: - debug_print(f"Allgemeiner Fehler beim Batch-Update: {type(e).__name__} - {e}") - raise e # Fehler weitergeben + if not self.sheet: debug_print("FEHLER: Keine Sheet-Verbindung für Batch-Update."); return False + if not update_data: return True + try: self.sheet.batch_update(update_data, value_input_option='USER_ENTERED'); return True + except gspread.exceptions.APIError as e: debug_print(f"Google API Fehler Batch-Update: Status {e.response.status_code} - {e.response.text[:500]}"); raise e + except Exception as e: debug_print(f"Allgemeiner Fehler Batch-Update: {type(e).__name__} - {e}"); raise e # --- Ende GoogleSheetHandler Klasse ---