This commit is contained in:
2025-05-07 06:33:21 +00:00
parent bf16531fee
commit 4b6eec93b5

View File

@@ -9389,7 +9389,7 @@ class DataProcessor:
# Stellen Sie sicher, dass die Header-Zeile auch die erwartete Mindestlaenge hat,
# um die Spa
# ==========================================================================
# ==========================================================================
# === Utility Methods (Other Specific Tasks) ===============================
# ==========================================================================
@@ -9472,7 +9472,7 @@ class DataProcessor:
if details_col_idx is None:
# Fallback auf 'Website Rohtext' (AR)
details_col_idx = COLUMN_MAP.get("Website Rohtext") # Block 1 Column Map
details_col_key_for_logging = "Website Rohtext" # Name fuer Logging
details_col_key_for_logging = "Website Rohtext"
# Pruefen Sie, ob der Fallback-Schluessel gefunden wurde
if details_col_idx is None:
self.logger.critical("FEHLER: Weder 'Website Details' noch 'Website Rohtext' Spaltenindex in COLUMN_MAP gefunden.")
@@ -9545,7 +9545,7 @@ class DataProcessor:
log_check = (i < start_sheet_row + 5) or (i % 100 == 0) or (processing_needed_for_row)
if log_check:
company_name = self._get_cell_value_safe(row, "CRM Name").strip() # Block 1 Column Map
self.logger.debug(f"Zeile {i} ({company_name[:50]}... Website Details Check): A='x'? {is_marked_for_processing}, D gueltig? {website_url_is_valid_looking}. Benötigt Verarbeitung? {processing_needed_for_row}") # Gekuerzt loggen
self.logger.debug(f"Zeile {i} ({company_name[:50]}... Website Details Check): A='x'? {is_marked_for_processing}, D gueltig? {website_url_is_valid_looking}. Benoetigt Verarbeitung? {processing_needed_for_row}") # Gekuerzt loggen
# Wenn die Verarbeitung fuer diese Zeile nicht noetig ist (trotz 'x' fehlte die URL)
@@ -9566,10 +9566,10 @@ class DataProcessor:
break # Brich die Schleife ab
self.logger.info(f"Zeile {i}: Extrahiere Website Details von {website_url[:100]}...") # Logge Start (gekuerzt)
selflogger.info(f"Zeile {i}: Extrahiere Website Details von {website_url[:100]}...") # Logge Start (gekuerzt)
details = "FEHLER: Funktion 'scrape_website_details' nicht verfuegbar" # Default Fehler, falls die Funktion nicht existiert (Sollte nicht passieren, wenn Sektion 3 korrekt ist)
details = "FEHLER: Funktion 'scrape_website_details' nicht verfuegbar" # Default Fehler, falls die Funktion nicht existiert (Sollte nicht passieren, wenn Block 13 korrekt ist)
try:
# Rufe die globale Funktion scrape_website_details auf (Block 13).
@@ -9589,8 +9589,7 @@ class DataProcessor:
except NameError:
# Dieser Fehler sollte nicht auftreten, wenn scrape_website_details in Sektion 3/7 ist.
# Wenn doch, deutet es auf ein schwerwiegendes Problem bei der Code-Organisation hin.
# Dieser Fehler sollte nicht auftreten, wenn scrape_website_details in Block 13 ist.
self.logger.critical("FEHLER: Funktion 'scrape_website_details' ist nicht definiert! Kann Details nicht extrahieren.")
# Logge den Traceback.
self.logger.debug(traceback.format_exc())
@@ -9598,7 +9597,7 @@ class DataProcessor:
except Exception as e_detail:
# Fange andere unerwartete Fehler ab, die nicht von scrape_website_details behandelt wurden.
self.logger.exception(f"Unerwarteter Fehler bei scrape_website_details fuer {website_url[:100]}...: {e_detail}") # Logge Fehler (gekuerzt) und Traceback
self.logger.exception(f"Unerwarteter Fehler bei scrape_website_details fuer {website_url[:100]}...: {type(e_detail).__name__} - {e_detail}") # Logge Fehler (gekuerzt) und Traceback
details = f"k.A. (Unerwarteter Fehler: {str(e_detail)[:100]}...)" # Signalisiert Fehler (gekuerzt)
@@ -9628,6 +9627,7 @@ class DataProcessor:
# Leere die gesammelten Updates nach dem Senden.
all_sheet_updates = []
# Kleine Pause nach jeder Extraktion (nutzt Config Block 1).
# Dieser Modus macht API calls (ueber scrape_website_details und dessen Helfer), also Pause einbauen.
pause_duration = getattr(Config, 'RETRY_DELAY', 5) * 0.2
@@ -9651,6 +9651,512 @@ class DataProcessor:
# Keine Pause nach diesem Modus noetig, da die naechste Aktion im Dispatcher (Block 34) folgt.
# --- Methode zum Verarbeiten von Wiki-Updates basierend auf ChatGPT Vorschlaegen ---
# Diese Methode verarbeitet Zeilen, in denen S gesetzt ist (nicht in Endzustand),
# prueft ob U eine valide und andere Wiki-URL ist und fuehrt entsprechende Updates durch.
# Basierend auf process_wiki_updates_from_chatgpt aus Teil 4.
# Nutzt interne Helfer: _get_cell_value_safe, _get_col_letter.
# Nutzt globale Helfer: COLUMN_MAP (Block 1), logger, Config (Block 1), time,
# is_valid_wikipedia_article_url (Block 12).
# Nutzt die uebergeordnete sheet_handler Instanz (Block 14).
def process_wiki_updates_from_chatgpt(self, start_sheet_row=None, end_sheet_row=None, limit=None):
"""
Identifiziert Zeilen, in denen Status S gesetzt ist, aber NICHT auf einem Endzustand
(OK, X (UPDATED/COPIED/INVALID)), prueft ob U eine *valide* und *andere* Wiki-URL ist.
- Wenn ja: Kopiert U->M, markiert S='X (URL Copied)', U='URL uebernommen', loescht
abhaengige Wiki-Spalten (N-V, AN, AO, AP, AX), setzt ReEval-Flag A='x'.
- Wenn nein (U keine URL, U==M, oder U ungueltig): LOESCHT den Inhalt von U und
markiert S als 'X (Invalid Suggestion)'.
Verarbeitet maximal limit Zeilen.
Args:
start_sheet_row (int, optional): Die 1-basierte Startzeile im Sheet. Defaults to None (ab Zeile 7).
end_sheet_row (int, optional): Die 1-basierte Endzeile im Sheet. Defaults to None (bis Ende Sheet).
limit (int, optional): Maximale Anzahl ZU PRUEFENDER Zeilen. Defaults to None (Unbegrenzt).
"""
# Verwenden Sie logger, da das Logging jetzt konfiguriert ist
# Logge die Konfiguration des Modus
self.logger.info(f"Starte Modus 'wiki_updates_from_chatgpt' (S, U, M, N-V, AN, AO, AX, AP, A). Bereich: {start_sheet_row if start_sheet_row is not None else 'Start'}-{end_sheet_row if end_sheet_row is not None else 'Ende'}, Limit: {limit if limit is not None else 'Unbegrenzt'}...")
# --- Daten laden ---
# Laden Sie Daten neu. Kein automatischer Startindex-Check noetig hier,
# da wir nach Status S suchen.
# Der load_data Aufruf ist mit retry_on_failure dekoriert (Block 2).
if not self.sheet_handler.load_data():
self.logger.error("Fehler beim Laden der Daten fuer Wiki Updates.")
return # Beende die Methode, wenn das Laden fehlschlaegt
# Holen Sie die gesamte Datenliste (inklusive Header) aus dem SheetHandler.
all_data = self.sheet_handler.get_all_data_with_headers()
# Annahme: header_rows ist als Attribut im SheetHandler verfuegbar (Block 14).
header_rows = self.sheet_handler._header_rows
total_sheet_rows = len(all_data) # Gesamtzahl der Zeilen im Sheet
# Standard Startzeile, wenn nicht manuell gesetzt
if start_sheet_row is None: start_sheet_row = header_rows + 1 # Standardmaessig ab erster Datenzeile (Zeile nach Headern)
# Berechne Endzeile, wenn nicht manuell gesetzt
if end_sheet_row is None: end_sheet_row = total_sheet_rows # Bis zur letzten Zeile
# Logge den Suchbereich fuer Status S
self.logger.info(f"Suchbereich fuer Status S: Sheet-Zeilen {start_sheet_row} bis {end_sheet_row}. Gesamtzeilen im Sheet: {total_sheet_rows}")
# Pruefe, ob der Bereich gueltig ist
if start_sheet_row > end_sheet_row or start_sheet_row > total_sheet_rows:
self.logger.info("Berechneter Start liegt nach dem Ende des Bereichs oder Sheets. Keine Zeilen zu verarbeiten.")
return # Beende die Methode, wenn der Bereich leer ist
# --- Indizes und Buchstaben ---
# Stellen Sie sicher, dass alle benoetigten Spalten in COLUMN_MAP (Block 1) vorhanden sind
required_keys = [
"Chat Wiki Konsistenzpruefung", "Chat Vorschlag Wiki Artikel", "Wiki URL", # S, U, M (Pruefkriterien / Daten)
"Wikipedia Timestamp", "Wiki Verif. Timestamp", "Timestamp letzte Pruefung", "Version", # AN, AX, AO, AP (Spalten zum Loeschen)
"ReEval Flag", # A (ReEval Flag setzen)
"Wiki Absatz", "Wiki Branche", "Wiki Umsatz", "Wiki Mitarbeiter", "Wiki Kategorien", # N-R (Spalten zum Loeschen)
"Chat Begruendung Wiki Inkonsistenz", "Begruendung bei Abweichung", # T, V (Spalten zum Loeschen)
# AY (SerpAPI Wiki Search Timestamp) wird ebenfalls geleert, da abhaengig von M.
"SerpAPI Wiki Search Timestamp" # AY (Spalte zum Leeren)
]
# Erstellen Sie ein Dictionary mit Schluesseln und Indizes
col_indices = {key: COLUMN_MAP.get(key) for key in required_keys}
# Pruefen Sie, ob alle benoetigten Schluessel in COLUMN_MAP gefunden wurden
if None in col_indices.values():
missing = [k for k, v in col_indices.items() if v is None]
self.logger.critical(f"FEHLER: Benoetigte Spaltenschluessel fehlen in COLUMN_MAP fuer process_wiki_updates_from_chatgpt: {missing}. Breche ab.")
return # Beende die Methode bei kritischem Fehler
# Ermitteln Sie die Spaltenbuchstaben fuer Updates/Leerung (nutzt interne Helfer _get_col_letter Block 14)
s_letter = self.sheet_handler._get_col_letter(col_indices["Chat Wiki Konsistenzpruefung"] + 1) # Status S
u_letter = self.sheet_handler._get_col_letter(col_indices["Chat Vorschlag Wiki Artikel"] + 1) # Vorschlag U
m_letter = self.sheet_handler._get_col_letter(col_indices["Wiki URL"] + 1) # Wiki URL M
a_letter = self.sheet_handler._get_col_letter(col_indices["ReEval Flag"] + 1) # ReEval Flag A
# Spalten N-V leeren.
# N ist Wiki Absatz, V ist Begruendung bei Abweichung.
n_idx = col_indices["Wiki Absatz"]
v_idx = col_indices["Begruendung bei Abweichung"]
# Erstellen Sie den Bereichsnamen (z.B. "N:V")
n_letter = self.sheet_handler._get_col_letter(n_idx + 1)
v_letter = self.sheet_handler._get_col_letter(v_idx + 1)
nv_range_letter = f'{n_letter}:{v_letter}' # z.B. N:V
# Erstellen Sie eine Liste von leeren Strings fuer diesen Bereich
empty_nv_values = [''] * (v_idx - n_idx + 1) # Anzahl der Spalten = V_Index - N_Index + 1
# Timestamps AN, AO, AP, AX, AY leeren.
# Diese werden von anderen Schritten gesetzt und sollen hier zurueckgesetzt werden.
an_letter = self.sheet_handler._get_col_letter(col_indices["Wikipedia Timestamp"] + 1) # AN (Wiki Extraction TS)
ao_letter = self.sheet_handler._get_col_letter(col_indices["Timestamp letzte Pruefung"] + 1) # AO (Chat Evaluation TS)
ap_letter = self.sheet_handler._get_col_letter(col_indices["Version"] + 1) # AP (Version)
ax_letter = self.sheet_handler._get_col_letter(col_indices["Wiki Verif. Timestamp"] + 1) # AX (Wiki Verif. TS)
ay_letter = self.sheet_handler._get_col_letter(col_indices["SerpAPI Wiki Search Timestamp"] + 1) # AY (SerpAPI Wiki TS)
# --- Verarbeitung ---
# Holen Sie die Batch-Groesse fuer Sheet-Updates aus Config (Block 1).
update_batch_row_limit = getattr(Config, 'UPDATE_BATCH_ROW_LIMIT', 50)
all_sheet_updates = [] # Gesammelte Updates fuer Batch-Schreiben ins Sheet (Liste von Dicts)
processed_rows_count = 0 # Zaehlt Zeilen, die geprueft werden (im Rahmen des Limits zaehlen).
skipped_count = 0 # Zaehlt Zeilen, die uebersprungen werden (Status S im Endzustand etc.).
updated_url_count = 0 # Zaehlt Zeilen, wo U -> M kopiert wurde.
cleared_suggestion_count = 0 # Zaehlt Zeilen, wo Vorschlag U geloescht wurde.
# Iteriere durch die Datenzeilen im definierten Bereich (1-basierte Sheet-Zeilennummer)
for i in range(start_sheet_row, end_sheet_row + 1):
row_index_in_list = i - 1 # 0-basierter Index in der all_data Liste
# Pruefen Sie, ob das Ende des Sheets erreicht wurde
if row_index_in_list >= total_sheet_rows: break # Ende des Sheets erreicht
row = all_data[row_index_in_list] # Die Rohdaten fuer diese Zeile
# Stellen Sie sicher, dass die Zeile nicht leer ist
if not any(cell and isinstance(cell, str) and cell.strip() for cell in row):
#self.logger.debug(f"Zeile {i}: Uebersprungen (Leere Zeile).") # Zu viel Laerm im Debug
skipped_count += 1 # Zaehlen als uebersprungene Zeile
continue # Springe zur naechsten Zeile
# --- Pruefung, ob Verarbeitung fuer diese Zeile noetig ist ---
# Kriterium: Status S ist gesetzt (nicht leer) UND NICHT einer der Endzustaende.
# Endzustaende: "OK", "X (UPDATED)", "X (URL COPIED)", "X (INVALID SUGGESTION)"
# Holen Sie den Wert aus Spalte S (Chat Wiki Konsistenzpruefung) (nutzt interne Helfer _get_cell_value_safe)
s_value = self._get_cell_value_safe(row, "Chat Wiki Konsistenzpruefung").strip() # Block 1 Column Map
s_value_upper = s_value.upper()
# Definieren Sie die Endzustaende (Grossbuchstaben)
s_end_states = ["OK", "X (UPDATED)", "X (URL COPIED)", "X (INVALID SUGGESTION)"]
# Verarbeitung ist noetig, wenn S nicht leer ist UND S NICHT im Endzustand ist.
processing_needed_for_row = s_value and s_value_upper not in s_end_states
# Loggen der Pruefergebnisse fuer diese Zeile auf Debug-Level
log_check = (i < start_sheet_row + 5) or (i % 100 == 0) or (processing_needed_for_row)
if log_check:
self.logger.debug(f"Zeile {i} (Wiki Update Check): Status S='{s_value}'. Benoetigt Verarbeitung? {processing_needed_for_row}")
# Wenn die Verarbeitung fuer diese Zeile nicht noetig ist
if not processing_needed_for_row:
skipped_count += 1 # Zaehlen als uebersprungene Zeile
continue # Springe zur naechsten Zeile
# --- Wenn Verarbeitung noetig: Pruefe Vorschlag U und handle ---
processed_rows_count += 1 # Zaehle die Zeile, die geprueft wird (im Rahmen des Limits zaehlen).
# Pruefe das Limit fuer verarbeitete Zeilen
if limit is not None and isinstance(limit, int) and limit > 0 and processed_rows_count > limit:
# Wenn das Limit erreicht ist und es ein positives Limit gibt
self.logger.info(f"Verarbeitungslimit ({limit}) fuer process_wiki_updates_from_chatgpt erreicht. Breche weitere Zeilenpruefung ab.")
break # Brich die Schleife ab
# Holen Sie die Werte aus Spalte U (Chat Vorschlag Wiki Artikel) und M (Wiki URL) (nutzt interne Helfer _get_cell_value_safe)
vorschlag_u = self._get_cell_value_safe(row, "Chat Vorschlag Wiki Artikel").strip() # Block 1 Column Map
url_m = self._get_cell_value_safe(row, "Wiki URL").strip() # Block 1 Column Map
self.logger.info(f"Zeile {i}: Pruefe Wiki-Vorschlag U='{vorschlag_u[:100]}...' (aktuell M='{url_m[:100]}...')...") # Gekuerzt loggen
is_update_candidate = False # Flag, ob U eine gueltige, neue URL ist, die uebernommen werden soll.
new_url = "" # Die URL, die ggf. in M kopiert wird.
# Kriterium 1: Ist Vorschlag U ueberhaupt ein String und sieht nach Wikipedia aus?
condition1_u_is_wiki_url = vorschlag_u and isinstance(vorschlag_u, str) and "wikipedia.org/wiki/" in vorschlag_u.lower() and vorschlag_u.lower().startswith(("http://", "https://")) # Check auf Schema hinzugefuegt
# Wenn der Vorschlag U wie eine Wikipedia-URL aussieht
if condition1_u_is_wiki_url:
new_url = vorschlag_u # Nehme den Vorschlag als potenzielle neue URL
# Kriterium 2: Unterscheidet sich der Vorschlag U von der aktuellen URL in M?
# Pruefe, ob die neue URL nicht identisch mit der aktuellen M-URL ist.
condition2_u_differs_m = new_url != url_m
# Wenn sich der Vorschlag U von der aktuellen M-URL unterscheidet
if condition2_u_differs_m:
self.logger.debug(f" -> Vorschlag U ({new_url[:100]}...) unterscheidet sich von M ({url_m[:100]}). Pruefe Validitaet...") # Gekuerzt loggen
# Kriterium 3: Ist die vorgeschlagene URL ein valider Wikipedia-Artikel (nicht Weiterleitung, Begriffsklaerung, Fehler)?
# Nutzt globale Funktion is_valid_wikipedia_article_url (Block 12) mit Retry Decorator (Block 2).
# is_valid_wikipedia_article_url wirft Exception bei endgueltigem Fehler.
try:
condition3_u_is_valid = is_valid_wikipedia_article_url(new_url) # Nutzt globalen Helfer (Block 12)
# Wenn die vorgeschlagene URL ein valider Artikel ist
if condition3_u_is_valid:
is_update_candidate = True # Alle Kriterien erfuellt! Der Vorschlag kann uebernommen werden.
self.logger.debug(f" -> URL '{new_url[:100]}...' ist ein VALIDER Artikel laut API Check.") # Gekuerzt loggen
else:
# Wenn die vorgeschlagene URL nicht valide ist
self.logger.debug(f" -> URL '{new_url[:100]}...' ist KEIN valider Artikel laut API Check.") # Gekuerzt loggen
except Exception as e_validity_check:
# Wenn is_valid_wikipedia_article_url eine Exception wirft (nach Retries)
# Der Fehler wird bereits vom retry_on_failure Decorator geloggt.
self.logger.error(f"FEHLER bei Validitaetspruefung von Vorschlag U '{new_url[:100]}...': {e_validity_check}") # Gekuerzt loggen
# Bei Fehler bleibt is_update_candidate False.
pass # Faert fort
else:
# Wenn der Vorschlag U identisch mit der aktuellen M-URL ist
self.logger.debug(f" -> Vorschlag U ist identisch mit URL M. Wird nicht uebernommen.")
else:
# Wenn der Vorschlag U nicht wie eine Wikipedia-URL aussieht
self.logger.debug(f" -> Vorschlag U ('{vorschlag_u[:100]}...') ist keine Wikipedia URL. Wird nicht uebernommen.") # Gekuerzt loggen
# --- Verarbeitung des Kandidaten ODER Loeschen des ungueltigen Vorschlags ---
updates_for_row = [] # Lokale Liste fuer Updates DIESER Zeile
if is_update_candidate:
# Fall 1: Gueltiges Update durchfuehren (Vorschlag U wird in M kopiert)
self.logger.info(f"Zeile {i}: Update-Kandidat VALIDIERUNG ERFOLGREICH. Kopiere U->M, setze ReEval-Flag 'x', loesche abhaengige Spalten.")
updated_url_count += 1 # Zaehle die uebernommene URL
# Updates sammeln (M, S, U, N-V, AN, AO, AP, AX, AY, A) (nutzt interne Helfer _get_col_letter Block 14)
updates_for_row.append({'range': f'{m_letter}{i}', 'values': [[new_url]]}) # Setze die neue URL in Spalte M (Block 1 Column Map)
updates_for_row.append({'range': f'{s_letter}{i}', 'values': [["X (URL Copied)"]]}) # Setze Status S auf "X (URL Copied)" (Block 1 Column Map)
updates_for_row.append({'range': f'{u_letter}{i}', 'values': [["URL uebernommen"]]}) # Schreibe Info in Spalte U (Block 1 Column Map)
updates_for_row.append({'range': f'{a_letter}{i}', 'values': [["x"]]}) # Setze ReEval Flag (A) auf 'x' (Block 1 Column Map)
# Leere Spalten N-V.
# Fuege das Update zum Leeren des Bereichs V-Y hinzu, falls der Bereichsname ermittelt werden konnte.
if nv_range_letter: # Pruefe, ob der Bereichsname ermittelt werden konnte.
updates_for_row.append({'range': f'{n_letter}{i}:{v_letter}{i}', 'values': [empty_nv_values]}) # Block 1 Column Map, lokale Variable
else:
self.logger.warning(f"Konnte Spaltenbereich N-V ({n_letter}:{v_letter}) fuer Leerung nicht ermitteln fuer Zeile {i}. Leerung uebersprungen.")
# Leere Timestamps AN, AO, AP, AX, AY.
# Dies setzt die Zeile zurueck, damit andere Schritte sie spaeter bearbeiten.
updates_for_row.append({'range': f'{an_letter}{i}', 'values': [['']]}) # AN (Wiki Extraction TS) Block 1 Column Map
updates_for_row.append({'range': f'{ao_letter}{i}', 'values': [['']]}) # AO (Chat Evaluation TS) Block 1 Column Map
updates_for_row.append({'range': f'{ap_letter}{i}', 'values': [['']]}) # AP (Version) Block 1 Column Map
updates_for_row.append({'range': f'{ax_letter}{i}', 'values': [['']]}) # AX (Wiki Verif. TS) Block 1 Column Map
updates_for_row.append({'range': f'{ay_letter}{i}', 'values': [['']]}) # AY (SerpAPI Wiki TS) Block 1 Column Map
else:
# Fall 2: Ungueltigen Vorschlag loeschen/markieren
# Wenn der Vorschlag U nicht uebernommen wird (weil ungueltig oder identisch mit M).
self.logger.info(f"Zeile {i}: Vorschlag U ('{vorschlag_u[:100]}...') ist ungueltig/identisch. Loesche U und setze Status S auf 'X (Invalid Suggestion)'.") # Gekuerzt loggen
cleared_suggestion_count += 1 # Zaehle den bereinigten Vorschlag
# Updates sammeln (S, U) (nutzt interne Helfer _get_col_letter Block 14)
updates_for_row.append({'range': f'{s_letter}{i}', 'values': [["X (Invalid Suggestion)"]]}) # Setze Status S auf "X (Invalid Suggestion)" (Block 1 Column Map)
updates_for_row.append({'range': f'{u_letter}{i}', 'values': [[""]]}) # Loesche den Vorschlag in Spalte U (Block 1 Column Map)
# KEIN ReEval-Flag (A) setzen in diesem Fall.
# Sammle die Updates fuer diese Zeile in der globalen Liste all_sheet_updates.
all_sheet_updates.extend(updates_for_row)
# Sende gesammelte Sheet Updates wenn das Update-Batch-Limit erreicht ist.
# update_batch_row_limit wird aus Config geholt (Block 1).
# Die Anzahl der Updates pro Zeile variiert stark (ca. 2 bei ungueltigem Vorschlag, ca. 10+ bei gueltigem).
# Pruefen Sie einfach die Laenge der gesammelten Liste.
if len(all_sheet_updates) >= update_batch_row_limit * 5: # Grobe Schaetzung: im Schnitt 5 Updates pro Zeile
self.logger.debug(f" Sende gesammelte Sheet-Updates ({len(all_sheet_updates)} Zellen)...")
# Nutzt die batch_update_cells Methode des Sheet Handlers (Block 14) mit Retry.
# Wenn es fehlschlaegt, wird es intern geloggt.
success = self.sheet_handler.batch_update_cells(all_sheet_updates)
if success:
self.logger.info(f" Sheet-Update fuer {len(all_sheet_updates)} Zellen erfolgreich.")
# Der Fehlerfall wird von batch_update_cells geloggt
# Leere die gesammelten Updates nach dem Senden.
all_sheet_updates = []
# Kleine Pause nach jeder geprueften Zeile (nutzt Config Block 1).
# Dieser Modus macht API calls (ueber is_valid_wikipedia_article_url Block 12), also Pause einbauen.
pause_duration = getattr(Config, 'RETRY_DELAY', 5) * 0.2
#self.logger.debug(f"Warte {pause_duration:.2f}s nach Pruefung...") # Zu viel Laerm im Debug
time.sleep(pause_duration)
# --- Finale Sheet Updates senden ---
# Sende alle verbleibenden gesammelten Updates in einem letzten Batch-Update.
if all_sheet_updates:
self.logger.info(f"Sende FINALE gesammelte Sheet-Updates ({len(all_sheet_updates)} Zellen)...")
# Nutzt die batch_update_cells Methode des Sheet Handlers (Block 14) mit Retry.
success = self.sheet_handler.batch_update_cells(all_sheet_updates)
if success:
self.logger.info(f"FINALES Sheet-Update erfolgreich.")
# Der Fehlerfall wird von batch_update_cells geloggt
# Logge den Abschluss des Modus
self.logger.info(f"Modus 'wiki_updates_from_chatgpt' abgeschlossen. {processed_rows_count} Zeilen geprueft, {updated_url_count} URLs kopiert & fuer ReEval markiert, {cleared_suggestion_count} ungueltige Vorschlaege geloescht/markiert, {skipped_count} Zeilen uebersprungen.")
# Keine Pause nach diesem Modus noetig, da die naechste Aktion im Dispatcher (Block 34) folgt.
# --- Methode zur Re-Extraktion von Wiki-Daten bei fehlendem Timestamp AN ---
# Diese Methode identifiziert Zeilen mit M gefuellt und AN leer und fuehrt _process_single_row (Block 19) fuer diese aus.
# Nutzt interne Helfer: _get_cell_value_safe, _process_single_row.
# Nutzt globale Helfer: COLUMN_MAP (Block 1), logger.
# Nutzt die uebergeordnete sheet_handler Instanz (Block 14).
def process_wiki_reextract_missing_an(self, start_sheet_row=None, end_sheet_row=None, limit=None):
"""
Identifiziert Zeilen, bei denen eine Wiki URL (M) vorhanden ist, aber der
Wikipedia Timestamp (AN) fehlt. Fuehrt _process_single_row fuer diese Zeilen aus,
beschraenkt auf den 'wiki'-Schritt und mit force_reeval=True, um die Extraktion
erneut zu versuchen.
Args:
start_sheet_row (int, optional): Die 1-basierte Startzeile im Sheet. Defaults to None (automatische Ermittlung basierend auf leeren AN).
end_sheet_row (int, optional): Die 1-basierte Endzeile im Sheet. Defaults to None (bis Ende Sheet).
limit (int, optional): Maximale Anzahl ZU VERARBEITENDER Zeilen. Defaults to None (Unbegrenzt).
"""
# Verwenden Sie logger, da das Logging jetzt konfiguriert ist
# Logge die Konfiguration des Modus
self.logger.info(f"Starte Modus 'wiki_reextract_missing_an' (M gefuellt & AN leer). Bereich: {start_sheet_row if start_sheet_row is not None else 'Start'}-{end_sheet_row if end_sheet_row is not None else 'Ende'}, Limit: {limit if limit is not None else 'Unbegrenzt'}...")
# --- Daten laden und Startzeile ermitteln ---
# Automatische Ermittlung der Startzeile, wenn nicht manuell gesetzt.
# Dieser Modus sucht nach leeren AN mit gefuelltem M. Die automatische Startzeile
# basierend auf leeren AN ist ein guter Startpunkt.
if start_sheet_row is None:
self.logger.info("Automatische Ermittlung der Startzeile basierend auf leeren AN...")
# Nutzt get_start_row_index des Sheet Handlers (Block 14). Prueft auf leeren AN (Block 1 Column Map).
# Standardmaessig ab Zeile 7
start_data_index_no_header = self.sheet_handler.get_start_row_index(check_column_key="Wikipedia Timestamp", min_sheet_row=7)
# Wenn get_start_row_index -1 zurueckgibt (Fehler)
if start_data_index_no_header == -1:
self.logger.error("FEHLER bei automatischer Ermittlung der Startzeile. Breche Modus ab.")
return # Beende die Methode
# Berechne die 1-basierte Sheet-Startzeile aus dem 0-basierten Daten-Index
start_sheet_row = start_data_index_no_header + self.sheet_handler._header_rows + 1 # Block 14 SheetHandler Attribut
self.logger.info(f"Automatisch ermittelte Startzeile (erste leere AN Zelle): {start_sheet_row}")
else:
# Wenn start_sheet_row manuell gesetzt wurde, laden Sie die Daten trotzdem neu, um aktuell zu sein.
# Der load_data Aufruf ist mit retry_on_failure dekoriert (Block 2).
if not self.sheet_handler.load_data():
self.logger.error("Fehler beim Laden der Daten fuer wiki_reextract_missing_an.")
return # Beende die Methode, wenn das Laden fehlschlaegt
# Holen Sie die gesamte Datenliste (inklusive Header) aus dem SheetHandler.
all_data = self.sheet_handler.get_all_data_with_headers();
# Annahme: header_rows ist als Attribut im SheetHandler verfuegbar (Block 14).
header_rows = self.sheet_handler._header_rows;
total_sheet_rows = len(all_data) # Gesamtzahl der Zeilen im Sheet
# Berechne Endzeile, wenn nicht manuell gesetzt
if end_sheet_row is None:
end_sheet_row = total_sheet_rows # Bis zur letzten Zeile
# Logge den verarbeitungsbereich
self.logger.info(f"Suchbereich fuer M gefuellt & AN leer: Sheet-Zeilen {start_sheet_row} bis {end_sheet_row}. Gesamtzeilen im Sheet: {total_sheet_rows}")
# Pruefe, ob der Bereich gueltig ist (Start <= Ende und Start nicht ueber Gesamtzeilen)
if start_sheet_row > end_sheet_row or start_sheet_row > total_sheet_rows:
self.logger.info("Berechneter Start liegt nach dem Ende des Bereichs oder Sheets. Keine Zeilen zu verarbeiten.")
return # Beende die Methode, wenn der Bereich leer ist
# --- Indizes ---
# Stellen Sie sicher, dass alle benoetigten Spalten in COLUMN_MAP (Block 1) vorhanden sind
required_keys = ["Wiki URL", "Wikipedia Timestamp"] # M, AN (Pruefkriterien)
# Erstellen Sie ein Dictionary mit Schluesseln und Indizes
col_indices = {key: COLUMN_MAP.get(key) for key in required_keys}
# Pruefen Sie, ob alle benoetigten Schluessel in COLUMN_MAP gefunden wurden
if None in col_indices.values():
missing = [k for k, v in col_indices.items() if v is None]
self.logger.critical(f"FEHLER: Benoetigte Spaltenschluessel fehlen in COLUMN_MAP fuer wiki_reextract_missing_an: {missing}. Breche ab.")
return # Beende die Methode bei kritischem Fehler
# Ermitteln Sie die Indizes
m_col_idx = col_indices["Wiki URL"]
an_col_idx = col_indices["Wikipedia Timestamp"]
# --- Verarbeitung ---
processed_count = 0 # Zaehlt Zeilen, die an _process_single_row uebergeben wurden (im Rahmen des Limits zaehlen).
skipped_count = 0 # Zaehlt Zeilen, die uebersprungen wurden.
# Iteriere durch die Datenzeilen im definierten Bereich (1-basierte Sheet-Zeilennummer)
for i in range(start_sheet_row, end_sheet_row + 1):
row_index_in_list = i - 1 # 0-basierter Index in der all_data Liste
# Pruefen Sie, ob das Ende des Sheets erreicht wurde
if row_index_in_list >= total_sheet_rows: break # Ende des Sheets erreicht
row = all_data[row_index_in_list] # Die Rohdaten fuer diese Zeile
# Stellen Sie sicher, dass die Zeile nicht leer ist
if not any(cell and isinstance(cell, str) and cell.strip() for cell in row):
#self.logger.debug(f"Zeile {i}: Uebersprungen (Leere Zeile).") # Zu viel Laerm im Debug
skipped_count += 1 # Zaehlen als uebersprungene Zeile
continue # Springe zur naechsten Zeile
# --- Pruefung, ob Verarbeitung fuer diese Zeile noetig ist ---
# Kriterium: Wiki URL (M) ist vorhanden und gueltig aussehend.
# UND Wikipedia Timestamp (AN) ist leer.
# Holen Sie die Werte aus den entsprechenden Spalten (nutzt interne Helfer _get_cell_value_safe)
m_value = self._get_cell_value_safe(row, "Wiki URL").strip() # Block 1 Column Map
an_value = self._get_cell_value_safe(row, "Wikipedia Timestamp").strip() # Block 1 Column Map
# Pruefen Sie, ob M gefuellt und gueltig aussieht.
is_m_valid_looking = m_value and isinstance(m_value, str) and "wikipedia.org/wiki/" in m_value.lower() and m_value.lower() not in ["k.a.", "kein artikel gefunden", "fehler bei suche", "http:"] # Fuege "http:" hinzu basierend auf Log
# Pruefen Sie, ob AN leer ist.
is_an_empty = not an_value
# Verarbeitung ist noetig, wenn M gueltig aussieht UND AN leer ist.
processing_needed_for_row = is_m_valid_looking and is_an_empty
# Loggen der Pruefergebnisse fuer diese Zeile auf Debug-Level
log_check = (i < start_sheet_row + 5) or (i % 100 == 0) or (processing_needed_for_row)
if log_check:
company_name = self._get_cell_value_safe(row, "CRM Name").strip() # Block 1 Column Map
self.logger.debug(f"Zeile {i} ({company_name[:50]}... Wiki Re-extract Check): M ('{m_value[:50]}...') gueltig? {is_m_valid_looking}, AN leer? {is_an_empty}. Benoetigt Verarbeitung? {processing_needed_for_row}") # Gekuerzt loggen
# Wenn die Verarbeitung fuer diese Zeile nicht noetig ist
if not processing_needed_for_row:
skipped_count += 1 # Zaehlen als uebersprungene Zeile
continue # Springe zur naechsten Zeile
# --- Wenn Verarbeitung noetig: Rufe _process_single_row auf ---
processed_count += 1 # Zaehle die Zeile, die an _process_single_row uebergeben wird (im Rahmen des Limits zaehlen)
# Pruefe das Limit fuer verarbeitete Zeilen
if limit is not None and isinstance(limit, int) and limit > 0 and processed_count > limit:
# Wenn das Limit erreicht ist und es ein positives Limit gibt
self.logger.info(f"Verarbeitungslimit ({limit}) fuer wiki_reextract_missing_an erreicht. Breche weitere Zeilenpruefung ab.")
break # Brich die Schleife ab
self.logger.info(f"Zeile {i}: M gefuellt & AN leer. Versuche Wiki-Re-Extraktion ueber _process_single_row...")
try:
# RUFE _process_single_row AUF (Block 19).
# Mit steps_to_run={'wiki'} und force_reeval=True,
# damit nur der Wiki-Schritt ausgefuehrt wird und Timestamps ignoriert werden.
# Im Re-Extract Modus loeschen wir das 'x'-Flag NICHT automatisch.
self._process_single_row(
row_num_in_sheet = i,
row_data = row, # Uebergibt die aktuellen Rohdaten der Zeile
steps_to_run = {'wiki'}, # <<< NUR der Wiki-Schritt soll laufen
force_reeval = True, # <<< Erzwingt die Ausfuehrung des 'wiki' Schritts (ignoriert AN, S).
clear_x_flag = False # <<< 'x'-Flag wird in diesem Modus NICHT geloescht
)
# _process_single_row (Block 19) loggt intern den Abschluss und fuehrt das Sheet-Update durch.
except Exception as e_proc:
# Wenn _process_single_row einen Fehler wirft (nachdem interne Retries aufgaben),
# fangen wir ihn hier, loggen ihn und fahren mit der naechsten Zeile fort.
self.logger.exception(f"FEHLER bei Verarbeitung von Zeile {i} in wiki_reextract_missing_an: {e_proc}")
# Hier koennen Sie z.B. einen Fehlerindikator in eine spezielle Spalte im Sheet schreiben lassen.
# Dieses Update muesste dann separat oder im naechsten Lauf behandelt werden.
# _process_single_row beinhaltet bereits eine kleine Pause am Ende.
# Hier ist keine zusaetzliche Pause noetig, wenn _process_single_row erfolgreich war.
# Wenn _process_single_row eine Exception wirft, kann hier eine kurze Pause sinnvoll sein.
# time.sleep(0.1) # Optional: Kurze Pause bei Fehler nach Exception
# Logge den Abschluss des Modus
self.logger.info(f"Modus 'wiki_reextract_missing_an' abgeschlossen. {processed_count} Zeilen an _process_single_row uebergeben, {skipped_count} Zeilen uebersprungen.")
# Keine Pause nach diesem Modus noetig, da die naechste Aktion im Dispatcher (Block 34) folgt.
# ==============================================================================
# Ende DataProcessor Klasse Utility: Other Specific Tasks Block
# ==============================================================================
# --- Methode zum Verarbeiten von Wiki-Updates basierend auf ChatGPT Vorschlaegen ---
# Diese Methode verarbeitet Zeilen, in denen S gesetzt ist (nicht in Endzustand),
# prueft ob U eine valide und andere Wiki-URL ist und fuehrt entsprechende Updates durch.