This commit is contained in:
2025-04-17 18:16:24 +00:00
parent d905c547ec
commit 04585f2b20

View File

@@ -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 ---