From 4b6eec93b5680e5016546667fafcd8988a44221f Mon Sep 17 00:00:00 2001 From: Floke Date: Wed, 7 May 2025 06:33:21 +0000 Subject: [PATCH] bugfix --- brancheneinstufung.py | 522 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 514 insertions(+), 8 deletions(-) diff --git a/brancheneinstufung.py b/brancheneinstufung.py index fffb2862..1f46377a 100644 --- a/brancheneinstufung.py +++ b/brancheneinstufung.py @@ -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.