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): def process_wiki_updates_from_chatgpt(sheet_handler, data_processor):
""" """
Identifiziert Zeilen, bei denen Wiki-Konsistenz (S) NICHT 'OK'/'X (Updated)' ist 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, 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)'. löscht Timestamps (AN, AX, AO, AP) und markiert S als 'X (Updated)'.
""" """
debug_print("Starte Modus: Wiki-Updates basierend auf ChatGPT-Vorschlägen...") debug_print("Starte Modus: Wiki-Updates basierend auf ChatGPT-Vorschlägen...")
if not sheet_handler.load_data(): if not sheet_handler.load_data(): return
debug_print("FEHLER: Konnte Sheet-Daten nicht laden für Wiki-Updates.")
return
all_data = sheet_handler.get_all_data_with_headers() all_data = sheet_handler.get_all_data_with_headers()
if not all_data or len(all_data) <= 5: if not all_data or len(all_data) <= 5: return
debug_print("FEHLER/WARNUNG: Keine Daten zum Verarbeiten in Wiki-Updates gefunden.")
return
header_rows = 5 header_rows = 5
data_rows = all_data[header_rows:] data_rows = all_data[header_rows:]
# --- Indizes holen --- # Indizes holen
required_keys = [ 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"]
"Chat Wiki Konsistenzprüfung", "Chat Vorschlag Wiki Artikel", "Wiki URL", col_indices = {}; all_keys_found = True
"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
for key in required_keys: for key in required_keys:
idx = COLUMN_MAP.get(key) idx = COLUMN_MAP.get(key)
if idx is None: if idx is None: debug_print(f"FEHLER: Schlüssel '{key}' fehlt!"); all_keys_found = False
debug_print(f"FEHLER: Schlüssel '{key}' für Spaltenindex fehlt in COLUMN_MAP!") col_indices[key] = idx
all_keys_found = False if not all_keys_found: return debug_print("Breche Wiki-Updates ab.")
col_indices[key] = idx # Speichere auch None, um Fehler unten zu vermeiden
if not all_keys_found:
debug_print("Breche Wiki-Updates ab, da Spaltenindizes fehlen.")
return
# --- Variablen für Verarbeitung ---
all_sheet_updates = [] all_sheet_updates = []
processed_rows_count = 0 processed_rows_count = 0
skipped_rows_count = 0
error_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): 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 --- def get_value(key): # Sicherer Zugriff auf Spaltenindex
# Funktion zum sicheren Holen von Werten aus der Zeile
def get_value(key):
index = col_indices.get(key) index = col_indices.get(key)
if index is not None and len(row) > index: if index is not None and len(row) > index: return row[index]
return row[index] return ""
return "" # Leerer String als Standard
konsistenz_s = get_value("Chat Wiki Konsistenzprüfung") 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") url_m = get_value("Wiki URL")
# --- Bedingung prüfen --- # Bedingung prüfen
is_update_candidate = False is_update_candidate = False; new_url = ""
new_url = ""
konsistenz_s_upper = konsistenz_s.strip().upper() 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() 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)", ""] condition1_status_nok = konsistenz_s_upper not in ["OK", "X (UPDATED)", ""]
condition2_u_is_url = vorschlag_u_cleaned.lower().startswith(("http://", "https://"))
# Bedingung 2: Vorschlag T ist eine valide URL condition3_u_differs_m = False
condition2_t_is_url = vorschlag_t_cleaned.lower().startswith(("http://", "https://")) 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
# 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
# Detailliertes Debugging für relevante Zeilen # 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: 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"\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")
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")
# --- Verarbeitung des Kandidaten ---
if is_update_candidate: if is_update_candidate:
debug_print(f"Zeile {row_num_in_sheet}: Verarbeite Update-Kandidat. Neue URL: {new_url}") debug_print(f"Zeile {row_num_in_sheet}: Verarbeite Update-Kandidat. Neue URL: {new_url}")
try: try:
# --- Wiki Reparse --- # Wiki Reparse
debug_print(f" -> Reparsing Wiki-Daten für {new_url}...") debug_print(f" -> Reparsing Wiki: {new_url}...")
new_wiki_data = wiki_scraper.extract_company_data(new_url) new_wiki_data = wiki_scraper.extract_company_data(new_url); time.sleep(0.2)
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")
# --- Branch Neuberechnung --- debug_print(f" -> Neuberechnung Branch...")
crm_branche = get_value("CRM 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)
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)
# 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 = [ row_updates = [
{'range': f'{m_l}{row_num_in_sheet}', 'values': [[new_url]]}, {'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.')]]}, {'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'{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'{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'{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'{an_l}{row_num_in_sheet}', 'values': [[""]]}, {'range': f'{ax_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'{ao_l}{row_num_in_sheet}', 'values': [[""]]}, {'range': f'{vorschlag_u_letter}{row_num_in_sheet}', 'values': [["Korrektur übernommen"]]},
{'range': f'{ap_l}{row_num_in_sheet}', 'values': [[""]]}, # Version leeren {'range': f'{konsistenz_s_letter}{row_num_in_sheet}', 'values': [["X (Updated)"]]},
{'range': f'{t_l}{row_num_in_sheet}', 'values': [["Korrektur übernommen"]]},
{'range': f'{s_l}{row_num_in_sheet}', 'values': [["X (Updated)"]]}, # Status aktualisieren
] ]
all_sheet_updates.extend(row_updates) 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.") 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: except Exception as e_row:
error_rows_count += 1 error_rows_count += 1
debug_print(f"FEHLER bei Verarbeitung von Update-Kandidat Zeile {row_num_in_sheet}: {e_row}") debug_print(f"FEHLER Verarbeitung Zeile {row_num_in_sheet}: {e_row}")
# Optional Traceback loggen: # import traceback; debug_print(traceback.format_exc()) # Bei Bedarf Traceback loggen
# import traceback; debug_print(traceback.format_exc())
# --- Batch Update am Ende --- # --- Batch Update am Ende ---
if all_sheet_updates: if all_sheet_updates:
debug_print(f"Sende Batch-Update für {processed_rows_count} korrigierte Wiki-Einträge ({len(all_sheet_updates)} Zellen)...") debug_print(f"BEREIT ZUM SENDEN: 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) # Optional: Logge Beispiel-Updates
if success: debug_print(f"Sheet-Update für Wiki-Korrekturen erfolgreich.") if len(all_sheet_updates) > 0: debug_print(f" -> Beispiel Update 1: {all_sheet_updates[0]}")
else: debug_print(f"FEHLER beim Sheet-Update für Wiki-Korrekturen.") if len(all_sheet_updates) > 15: debug_print(f" -> Beispiel Update 15: {all_sheet_updates[14]}")
else:
debug_print("Keine Zeilen gefunden, die eine Wiki-URL-Korrektur benötigen/erfüllen.")
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): def extract_numeric_value(raw_value, is_umsatz=False):
"""Extrahiert und normalisiert Zahlenwerte (Umsatz in Mio, Mitarbeiter).""" """Extrahiert und normalisiert Zahlenwerte (Umsatz in Mio, Mitarbeiter)."""
@@ -1136,203 +1092,123 @@ def token_count(text):
class GoogleSheetHandler: class GoogleSheetHandler:
def __init__(self): def __init__(self):
"""Initialisiert den Handler, verbindet und lädt initiale Daten."""
self.sheet = None self.sheet = None
self.sheet_values = [] self.sheet_values = []
self.headers = [] # Speichert die erste Zeile als Header-Namen self.headers = []
try: try:
self._connect() self._connect()
if self.sheet: if self.sheet:
self.load_data() # Erste Datenladung bei Initialisierung self.load_data()
except Exception as e: except Exception as e:
debug_print(f"FATAL: Fehler bei Initialisierung von GoogleSheetHandler: {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}") raise ConnectionError(f"Google Sheet Handler Init failed: {e}")
# retry_on_failure Decorator sollte hier angewendet werden
@retry_on_failure @retry_on_failure
def _connect(self): def _connect(self):
"""Stellt Verbindung zum Google Sheet her.""" self.sheet = None
self.sheet = None # Sicherstellen, dass sheet vor try None ist
debug_print("Verbinde mit Google Sheets...") debug_print("Verbinde mit Google Sheets...")
try: try:
scope = ["https://www.googleapis.com/auth/spreadsheets"] scope = ["https://www.googleapis.com/auth/spreadsheets"]
creds = ServiceAccountCredentials.from_json_keyfile_name(CREDENTIALS_FILE, scope) creds = ServiceAccountCredentials.from_json_keyfile_name(CREDENTIALS_FILE, scope)
gc = gspread.authorize(creds) gc = gspread.authorize(creds)
sh = gc.open_by_url(Config.SHEET_URL) 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.") debug_print("Verbindung zu Google Sheets erfolgreich.")
except gspread.exceptions.APIError as e: 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]}") 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: except Exception as e:
# Logge andere Verbindungsfehler
debug_print(f"FEHLER bei der Google Sheets Verbindung: {type(e).__name__} - {e}") 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 @retry_on_failure
def load_data(self): def load_data(self):
"""Lädt alle Daten aus dem Sheet und aktualisiert self.sheet_values und self.headers."""
if not self.sheet: if not self.sheet:
debug_print("Fehler: Keine Sheet-Verbindung zum Laden der Daten.") debug_print("Fehler: Keine Sheet-Verbindung zum Laden der Daten.")
self.sheet_values = [] self.sheet_values = []
self.headers = [] self.headers = []
return False # Signalisiert Fehler return False
debug_print("Lade Daten aus Google Sheet...") debug_print("Lade Daten aus Google Sheet...")
try: 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: if not self.sheet_values:
debug_print("Warnung: Google Sheet scheint leer zu sein oder keine Daten zurückgegeben.") debug_print("Warnung: Google Sheet scheint leer zu sein oder keine Daten zurückgegeben.")
self.headers = [] self.headers = []
elif len(self.sheet_values) >= 1: return True
self.headers = self.sheet_values[0] # Speichere die erste Zeile als Header if len(self.sheet_values) >= 1: self.headers = self.sheet_values[0]
else: else: self.headers = []
self.headers = [] # Sollte nicht passieren, wenn sheet_values nicht leer war
debug_print(f"Daten neu geladen: {len(self.sheet_values)} Zeilen insgesamt.") debug_print(f"Daten neu geladen: {len(self.sheet_values)} Zeilen insgesamt.")
return True # Signalisiert Erfolg return True
except gspread.exceptions.APIError as e: 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]}") 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: except Exception as e:
debug_print(f"Allgemeiner Fehler beim Laden der Google Sheet Daten: {e}") debug_print(f"Allgemeiner Fehler beim Laden der Google Sheet Daten: {e}")
raise e # Damit retry greift raise e
def get_data(self): def get_data(self):
"""Gibt die aktuell im Handler gespeicherten Daten zurück (ohne die ersten 5 Header-Zeilen).""" header_rows = 5
header_rows = 5 # Definiert die Anzahl der zu überspringenden Header-Zeilen
if not self.sheet_values or len(self.sheet_values) <= header_rows: 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.") debug_print(f"Warnung in get_data: Nur {len(self.sheet_values)} Zeilen vorhanden, weniger als {header_rows} Header-Zeilen erwartet.")
return [] return []
# Gibt eine Slice der Liste zurück, die die Datenzeilen enthält
return self.sheet_values[header_rows:] return self.sheet_values[header_rows:]
def get_all_data_with_headers(self): def get_all_data_with_headers(self):
"""Gibt alle aktuell im Handler gespeicherten Daten inklusive Header zurück."""
if not self.sheet_values: if not self.sheet_values:
debug_print("Warnung in get_all_data_with_headers: Keine Daten im Handler gespeichert.") 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? return []
# self.load_data() # Erneuter Ladeversuch
# return self.sheet_values
return [] # Gib leere Liste zurück, wenn nichts geladen ist
return self.sheet_values return self.sheet_values
def _get_col_letter(self, col_idx_1_based): 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
string = "" if n < 1: return None
n = col_idx_1_based while n > 0: n, remainder = divmod(n - 1, 26); string = chr(65 + remainder) + string
if n < 1: return None # Ungültiger Index
while n > 0:
n, remainder = divmod(n - 1, 26)
string = chr(65 + remainder) + string
return string return string
# --- ANGEPASST: Sucht jetzt nur noch nach EXAKT LEER ("") ---
def get_start_row_index(self, check_column_key, min_sheet_row=7): def get_start_row_index(self, check_column_key, min_sheet_row=7):
""" if not self.load_data(): return -1
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
header_rows = 5 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) check_column_index = COLUMN_MAP.get(check_column_key)
if check_column_index is None: if check_column_index is None:
debug_print(f"FEHLER: Schlüssel '{check_column_key}' nicht in COLUMN_MAP gefunden!") 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) 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) 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} 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} (Sheet-Zeile {search_start_index_in_data + header_rows + 1}) nach EXAKT LEEREM Wert (=='') in Spalte '{check_column_key}' ({actual_col_letter})...")
if search_start_index_in_data >= len(data_rows): 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.") debug_print(f"Start-Suchindex ({search_start_index_in_data}) >= Datenlänge ({len(data_rows)}).")
return len(data_rows) # Index nach der letzten Zeile return len(data_rows)
# Durchlaufe die Datenzeilen ab dem berechneten Startindex
for i in range(search_start_index_in_data, len(data_rows)): for i in range(search_start_index_in_data, len(data_rows)):
row = data_rows[i] row = data_rows[i]
current_sheet_row = i + header_rows + 1 current_sheet_row = i + header_rows + 1
cell_value = ""; is_exactly_empty = True
cell_value = "" # Standardwert, falls Spalte nicht existiert
is_exactly_empty = False
if len(row) > check_column_index: if len(row) > check_column_index:
cell_value = row[check_column_index] # Hole den Rohwert cell_value = row[check_column_index]
# Prüfe, ob der Wert EXAKT ein leerer String ist if cell_value != "": is_exactly_empty = False
if cell_value == "": log_debug = (i == search_start_index_in_data or i % 1000 == 0 or is_exactly_empty or i in range(10110, 10116))
is_exactly_empty = True 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}")
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}")
if 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})") 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) 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}.") 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 return last_index
# retry_on_failure Decorator sollte hier angewendet werden
@retry_on_failure @retry_on_failure
def batch_update_cells(self, update_data): def batch_update_cells(self, update_data):
""" if not self.sheet: debug_print("FEHLER: Keine Sheet-Verbindung für Batch-Update."); return False
Führt ein Batch-Update im Google Sheet durch. Beinhaltet Fehlerbehandlung. if not update_data: return True
try: self.sheet.batch_update(update_data, value_input_option='USER_ENTERED'); return True
Args: 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
update_data (list): Eine Liste von Dictionaries, jedes mit 'range' und 'values'. except Exception as e: debug_print(f"Allgemeiner Fehler Batch-Update: {type(e).__name__} - {e}"); raise e
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
# --- Ende GoogleSheetHandler Klasse --- # --- Ende GoogleSheetHandler Klasse ---