From fb34310ac8b69a9f3958d417dfd4ee7d34e9aaa8 Mon Sep 17 00:00:00 2001 From: Floke Date: Sun, 11 May 2025 07:19:51 +0000 Subject: [PATCH] bugfix --- brancheneinstufung.py | 374 ++++++++++++++++-------------------------- 1 file changed, 141 insertions(+), 233 deletions(-) diff --git a/brancheneinstufung.py b/brancheneinstufung.py index cea4ba9d6..8b6a27d66 100644 --- a/brancheneinstufung.py +++ b/brancheneinstufung.py @@ -4068,240 +4068,162 @@ class DataProcessor: # --- Der Code fuer den naechsten Verarbeitungsschritt (Wikipedia) folgt im naechsten Block --- # Definition der Methode _process_single_row wird in der naechsten Nachricht fortgesetzt. - # --- 2. Wikipedia Handling (Search, Extraction, Status Reset) --- + # ====================================================================== + # === 2. Wikipedia Handling (Search, Extraction, Status Reset) ========== + # ====================================================================== # Dieser Schritt wird ausgefuehrt, wenn 'wiki' in steps_to_run enthalten ist UND # (_needs_wiki_processing True ist ODER force_reeval True ist). - # _needs_wiki_processing prueft AN und S='X (URL Copied)'. - # Die Logik fuer S='X (URL Copied)' dient dazu, eine URL, die durch die Wiki-Update - # Funktion in M kopiert wurde, sofort neu extrahieren zu lassen. - # Nutzt interne Helfer: _get_cell_value_safe, _needs_wiki_processing. - # Nutzt globale Helfer: COLUMN_MAP, logger, wikipedia, wikipedia.exceptions, - # retry_on_failure, unquote, time, traceback. - # Nutzt die uebergebene wiki_scraper Instanz. - - # Pruefen Sie, ob der Wiki-Schritt im aktuellen Lauf angefordert wurde + # _needs_wiki_processing prueft V (alt AN) und Y="X (URL Copied)" (alt S). + run_wiki_step = 'wiki' in steps_to_run - # Pruefen Sie, ob der Wiki-Schritt laut Status oder Re-Eval noetig ist + # _needs_wiki_processing (Block 18) nutzt die NEUEN Spaltennamen für Timestamps/Status wiki_processing_needed_based_on_status = self._needs_wiki_processing(row_data, force_reeval) - - # Wenn der Wiki-Schritt angefordert wurde UND laut Status/Re-Eval noetig ist if run_wiki_step and wiki_processing_needed_based_on_status: - any_processing_done = True # Markiere, dass in dieser Zeile etwas getan wird + any_processing_done = True - # Bestimme den Grund fuer die Ausfuehrung dieses Schritts fuer das Logging grund_message_parts = [] if force_reeval: grund_message_parts.append('Re-Eval') - # Pruefe, ob der Timestamp AN leer ist (nutzt interne Helfer) - if not self._get_cell_value_safe(row_data, "Wikipedia Timestamp").strip(): grund_message_parts.append('AN leer') - # Pruefe, ob Status S "X (URL Copied)" ist (nutzt interne Helfer) - if self._get_cell_value_safe(row_data, "Chat Wiki Konsistenzpruefung").strip().upper() == "X (URL COPIED)": grund_message_parts.append("S='X (URL Copied)'") - grund_message = ", ".join(grund_message_parts) + if not self._get_cell_value_safe(row_data, "Wikipedia Timestamp").strip(): grund_message_parts.append('V (Wikipedia Timestamp) leer') # Neuer Schlüssel + if self._get_cell_value_safe(row_data, "Chat Wiki Konsistenzpruefung").strip().upper() == "X (URL COPIED)": grund_message_parts.append("Y (Chat Wiki Konsistenzpruefung)='X (URL Copied)'") # Neuer Schlüssel + grund_message = ", ".join(filter(None, grund_message_parts)) or "Unbekannter Grund" - self.logger.info(f"Zeile {row_num_in_sheet}: Fuehre WIKI Suche/Extraktion aus (Grund: {grund_message})...") # <<< GEÄNDERT + self.logger.info(f"Zeile {row_num_in_sheet}: Fuehre WIKI Suche/Extraktion aus (Grund: {grund_message})...") - # Holen Sie die aktuelle Wiki URL aus Spalte M (nutzt interne Helfer) - url_in_m = self._get_cell_value_safe(row_data, "Wiki URL").strip() - url_to_extract = None # Die URL, von der wir am Ende Daten extrahieren werden - search_was_needed = False # Flag, ob eine neue Suche durchgefuehrt wurde + # Aktuelle Wiki URL aus Spalte N (alt M) + url_in_n_sheet = self._get_cell_value_safe(row_data, "Wiki URL").strip() # Neuer Schlüssel "Wiki URL" + url_to_extract_from = None + search_was_needed_flag = False + status_y_indicates_reparse = self._get_cell_value_safe(row_data, "Chat Wiki Konsistenzpruefung").strip().upper() == "X (URL COPIED)" # Neuer Schlüssel + timestamp_v_is_empty = not self._get_cell_value_safe(row_data, "Wikipedia Timestamp").strip() # Neuer Schlüssel + + n_url_looks_valid_in_sheet = url_in_n_sheet and isinstance(url_in_n_sheet, str) and "wikipedia.org/wiki/" in url_in_n_sheet.lower() and url_in_n_sheet.lower() not in ["k.a.", "kein artikel gefunden", "fehler bei suche", "http:"] - # --- Logik zur Bestimmung der URL, die verwendet werden soll --- - # Prioritaet (bei Ausfuehrung des Wiki-Schritts): - # 1. Wenn Status S == "X (URL Copied)": Ignoriere URL in M, fuehre neue Suche aus. - # 2. Wenn force_reeval True: Nimm URL in M, WENN gueltig aussehend. Sonst neue Suche. - # 3. Wenn AN leer (und kein S="X(URL Copied)", kein Re-Eval): Nimm URL in M, WENN valide. Sonst neue Suche. - - status_s_indicates_reparse = self._get_cell_value_safe(row_data, "Chat Wiki Konsistenzpruefung").strip().upper() == "X (URL COPIED)" - an_value = self._get_cell_value_safe(row_data, "Wikipedia Timestamp").strip() - # Pruefe, ob die URL in M existiert und wie eine Wikipedia-URL aussieht (ignoriert "k.A." und Fehlerwerte) - m_url_exists_and_looks_valid = url_in_m and isinstance(url_in_m, str) and "wikipedia.org/wiki/" in url_in_m.lower() and url_in_m.lower() not in ["k.a.", "kein artikel gefunden", "fehler bei suche", "http:"] # Fuege "http:" hinzu - - - # Bestimmen Sie, ob eine neue Suche notwendig ist - if status_s_indicates_reparse: - # Wenn Status S signalisiert, dass eine neu kopierte URL extrahiert werden soll, fuehre immer eine Suche aus. - self.logger.warning(f" -> Status S ist 'X (URL Copied)', ignoriere URL '{url_in_m[:100]}...' in M und starte neue Suche...") # <<< GEÄNDERT - search_was_needed = True # Suche ist noetig - + # --- Logik zur Bestimmung der URL und Priorisierung von SerpAPI --- + if status_y_indicates_reparse: + self.logger.info(f" -> Status Y ist 'X (URL Copied)', ignoriere URL '{url_in_n_sheet[:100]}...' in N. Starte neue Suche (primär SerpAPI)...") + search_was_needed_flag = True elif force_reeval: - # Wenn Re-Eval erzwungen wird - self.logger.debug(" -> Re-Eval Modus aktiv fuer Wiki-Schritt.") # <<< GEÄNDERT - # Wenn die URL in M existiert und gueltig aussieht - if m_url_exists_and_looks_valid: - # Im Re-Eval Modus nehmen wir die URL aus M an, OHNE erneute Validierung oder Suche (Vertrauen auf M!). - self.logger.info(f" -> Re-Eval: Nutze vorhandene URL aus Spalte M direkt: {url_in_m[:100]}...") # <<< GEÄNDERT - url_to_extract = url_in_m # Verwende die URL aus M direkt + self.logger.debug(" -> Re-Eval Modus aktiv fuer Wiki-Schritt.") + if n_url_looks_valid_in_sheet: + self.logger.info(f" -> Re-Eval: Nutze vorhandene URL aus Spalte N direkt: {url_in_n_sheet[:100]}...") + url_to_extract_from = url_in_n_sheet else: - # Wenn M leer/ungueltig ist, auch im Re-Eval Modus neu suchen - self.logger.debug(f" -> Re-Eval: Spalte M ist leer oder ungueltig ('{url_in_m[:100]}...'). Starte neue Suche...") # <<< GEÄNDERT - search_was_needed = True # Suche ist noetig - - elif not an_value: - # Wenn AN leer ist (und kein S="X(Copied)" oder Re-Eval) - # Wenn die URL in M existiert und gueltig aussieht - if m_url_exists_and_looks_valid: - # Wenn AN fehlt und M gefuellt ist, pruefen wir die Validitaet der M-URL ueber die wikipedia Bibliothek. - self.logger.debug(f" -> AN fehlt, pruefe Validitaet der URL aus M: {url_in_m[:100]}...") # <<< GEÄNDERT + self.logger.debug(f" -> Re-Eval: Spalte N leer/ungueltig ('{url_in_n_sheet[:100]}...'). Starte neue Suche (primär SerpAPI)...") + search_was_needed_flag = True + elif timestamp_v_is_empty: # Wikipedia Timestamp (V, alt AN) ist leer + if n_url_looks_valid_in_sheet: + self.logger.debug(f" -> Wikipedia Timestamp (V) fehlt, pruefe Validitaet der URL aus N: {url_in_n_sheet[:100]}...") try: - # Extrahieren des Titels aus der URL fuer wikipedia.page (nutzt globale Helfer) - # Dieser Aufruf kann Exceptions werfen (PageError, DisambiguationError). - title_from_url_part = url_in_m.split('/wiki/', 1)[1].split('#')[0] # Titelteil nach /wiki/, Anker entfernen - title_from_url = unquote(title_from_url_part).replace('_', ' ') # Dekodieren und Unterstriche ersetzen - - # Laden des Page Objekts, um es mit _validate_article zu pruefen. - # wikipedia.page nutzt intern Requests und API calls, die fehlschlagen koennen. - # wikipedia.page selbst kann wikipedia.exceptions werfen. - # Wir fangen diese spezifischen wikipedia.exceptions hier ab, aber andere RequestsExceptions - # werden vom retry_on_failure auf extract_company_data gefangen (spaeter). - page_from_m = wikipedia.page(title_from_url, auto_suggest=False, preload=True) - - # Validierung des Artikels mit der Scraper-Methode (nutzt interne Methode) - # _validate_article kann interne Fehler haben (z.B. bei HTML Parsing), aber faengt sie. - if self.wiki_scraper._validate_article(page_from_m, company_name, website_url): - url_to_extract = page_from_m.url # Die URL ist valide und wird verwendet - self.logger.info(f" -> Vorhandene URL aus M '{url_to_extract[:100]}...' ist valide und wird verwendet.") # <<< GEÄNDERT + # Validierung mit der wiki_scraper Instanz + # _validate_article erwartet ein Page-Objekt. Wir müssen es zuerst holen. + title_from_url_part = url_in_n_sheet.split('/wiki/', 1)[1].split('#')[0] + title_from_url = unquote(title_from_url_part).replace('_', ' ') + page_object_from_n = wikipedia.page(title_from_url, auto_suggest=False, preload=True) # Kann Exceptions werfen + + if self.wiki_scraper._validate_article(page_object_from_n, company_name, website_url): + url_to_extract_from = page_object_from_n.url + self.logger.info(f" -> Vorhandene URL aus N '{url_to_extract_from[:100]}...' ist valide und wird verwendet.") else: - # Wenn der Artikel aus M nicht validiert wird - self.logger.warning(f" -> Vorhandene URL aus M '{page_from_m.title[:100]}...' ist NICHT valide. Starte neue Suche...") # <<< GEÄNDERT - search_was_needed = True # Suche ist noetig - - except (wikipedia.exceptions.PageError, wikipedia.exceptions.DisambiguationError) as e_wiki_m: - # Wenn die URL in M zu einem nicht existierenden Artikel oder einer Begriffsklaerung fuehrt - self.logger.warning(f" -> Vorhandene URL aus M '{url_in_m[:100]}...' fuehrt zu Fehler ({type(e_wiki_m).__name__}). Starte neue Suche...") # <<< GEÄNDERT - # Logge die Disambiguation Optionen auf Debug, falls vorhanden - if isinstance(e_wiki_m, wikipedia.exceptions.DisambiguationError): - self.logger.debug(f" -> Disambiguation Optionen: {str(e_wiki_m.options)[:100]}...") # <<< GEÄNDERT - search_was_needed = True # Suche ist noetig - pass # Faert fort - - except Exception as e_val_m: - # Fange andere unerwartete Fehler beim Pruefen der URL aus M ab (z.B. URL-Parsing-Fehler vor wikipedia.page) - self.logger.exception(f" -> Unerwarteter Fehler beim Pruefen der URL aus M '{url_in_m[:100]}...': {e_val_m}. Starte neue Suche...") # <<< GEÄNDERT - search_was_needed = True # Suche ist noetig - pass # Faert fort - + self.logger.warning(f" -> Vorhandene URL aus N '{page_object_from_n.title[:100]}...' ist NICHT valide. Starte neue Suche (primär SerpAPI)...") + search_was_needed_flag = True + except (wikipedia.exceptions.PageError, wikipedia.exceptions.DisambiguationError) as e_wiki_val: + self.logger.warning(f" -> Vorhandene URL aus N '{url_in_n_sheet[:100]}...' fuehrt zu Fehler ({type(e_wiki_val).__name__}). Starte neue Suche (primär SerpAPI)...") + if isinstance(e_wiki_val, wikipedia.exceptions.DisambiguationError): + self.logger.debug(f" -> Disambiguation Optionen: {str(e_wiki_val.options)[:100]}...") + search_was_needed_flag = True + except Exception as e_val_m_general: + self.logger.exception(f" -> Unerwarteter Fehler beim Pruefen/Laden der URL aus N '{url_in_n_sheet[:100]}...': {e_val_m_general}. Starte neue Suche (primär SerpAPI)...") + search_was_needed_flag = True else: - # M ist leer/ungueltig und AN fehlt -> Suche starten - self.logger.debug(f" -> AN fehlt und M leer/ungueltig ('{url_in_m[:100]}...'). Starte Wikipedia-Suche fuer '{company_name[:100]}...'...") # <<< GEÄNDERT - search_was_needed = True # Suche ist noetig - - # --- Führe die Suche aus, wenn search_was_needed True ist --- - if search_was_needed: - self.logger.debug(f" -> Fuehre Wikipedia Suche ueber scraper durch...") # <<< GEÄNDERT + self.logger.debug(f" -> Wikipedia Timestamp (V) fehlt und N leer/ungueltig ('{url_in_n_sheet[:100]}...'). Starte Wikipedia-Suche (primär SerpAPI) fuer '{company_name[:50]}...'...") + search_was_needed_flag = True + + if search_was_needed_flag: + self.logger.info(f" -> Suche nach Wikipedia-Artikel für '{company_name[:50]}...' via SerpAPI...") + search_name_for_serp = crm_kurzform if crm_kurzform and len(crm_kurzform) > 2 else company_name + self.logger.debug(f" Verwende Suchnamen für SerpAPI: '{search_name_for_serp}'") try: - # Rufe die search_company_article Methode des Scrapers auf. - # search_company_article ist mit retry_on_failure dekoriert und wirft bei endgueltigem Fehler eine Exception. - # Nutzt die ggf. neue Website URL fuer Kontext im search_company_article. - validated_page = self.wiki_scraper.search_company_article(company_name, website_url) # Nutzt die uebergebene scraper Instanz - - if validated_page: - # Wenn ein validierter Artikel gefunden wurde, setze die URL, von der extrahiert werden soll. - url_to_extract = validated_page.url - self.logger.info(f" -> Suche erfolgreich, validierte URL: {url_to_extract[:100]}...") # <<< GEÄNDERT - else: - # Wenn die Suche keinen validierten Artikel fand - self.logger.debug(f" -> Suche fand keinen validierten Artikel fuer '{company_name[:100]}...'.") # <<< GEÄNDERT - url_to_extract = 'Kein Artikel gefunden' # Signalisiert kein Artikel gefunden - - except Exception as e_wiki_search: - # Wenn search_company_article eine Exception wirft (nach Retries) - # Der Fehler wird bereits vom retry_on_failure Decorator geloggt. - self.logger.error(f"FEHLER bei Wikipedia Suche fuer '{company_name[:100]}...': {e_wiki_search}") # <<< GEÄNDERT - url_to_extract = f"FEHLER bei Suche: {str(e_wiki_search)[:50]}..." # Signalisiert Fehler bei Suche (gekuerzt) - # Pass, faert fort, um zumindest den Status zu setzen. - pass - - - # --- Datenextraktion, wenn eine URL bestimmt wurde, von der extrahiert werden soll --- - # Extrahiere Daten, wenn url_to_extract einen Wert hat, der NICHT "Kein Artikel gefunden" oder ein Fehlerstring ist. - if url_to_extract and isinstance(url_to_extract, str) and url_to_extract.lower() not in ['kein artikel gefunden'] and not url_to_extract.startswith("FEHLER"): - self.logger.debug(f" -> Extrahiere Daten von URL: {url_to_extract[:100]}...") # <<< GEÄNDERT - try: - # Rufe die extract_company_data Methode des Scrapers auf. - # extract_company_data ist mit retry_on_failure dekoriert und wirft bei endgueltigem Fehler eine Exception. - extracted_data = self.wiki_scraper.extract_company_data(url_to_extract) # Nutzt die uebergebene scraper Instanz - - # Pruefen Sie, ob die Extraktion erfolgreich war (nicht None oder mit Fehlerwert) - if extracted_data and isinstance(extracted_data, dict) and extracted_data.get('url') != 'k.A.': # Pruefe auf gueltige Extraktion - final_wiki_data = extracted_data # Aktualisiere die Arbeitskopie der Wiki-Daten mit den extrahierten Daten. - wiki_data_updated_in_this_run = True # Markieren, dass extrahierte Daten da sind (Trigger fuer Chat). - self.logger.info(f" -> Datenextraktion von {url_to_extract[:100]}... erfolgreich.") # <<< GEÄNDERT + # serp_wikipedia_lookup ist global und nutzt retry + new_url_from_serp = serp_wikipedia_lookup(search_name_for_serp, website=website_url) + if new_url_from_serp: + url_to_extract_from = new_url_from_serp + self.logger.info(f" -> SerpAPI Suche erfolgreich, gefundene URL: {url_to_extract_from[:100]}...") else: - # Wenn extrahierte Daten leer oder ungueltig sind (z.B. parse Fehler intern) - self.logger.error(f" -> Fehler bei Datenextraktion von {url_to_extract[:100]}... oder Extraktion war leer. Setze Daten auf 'k.A.'") # <<< GEÄNDERT - # Behalte die URL, aber setze alle anderen Felder auf k.A. oder Fehler. - final_wiki_data = {'url': url_to_extract, 'first_paragraph': 'k.A. (Extraktion fehlgeschlagen)', 'branche': 'k.A.', 'umsatz': 'k.A.', 'mitarbeiter': 'k.A.', 'categories': 'k.A.'} - wiki_data_updated_in_this_run = True # Markieren, dass die Daten ueberschrieben werden. - + self.logger.warning(f" -> SerpAPI Suche fand keinen passenden Wikipedia-Artikel für '{search_name_for_serp}'.") + url_to_extract_from = 'Kein Artikel gefunden' # Expliziter Wert + except Exception as e_serp_wiki_search: + self.logger.error(f"FEHLER bei SerpAPI Wikipedia Suche für '{search_name_for_serp}': {e_serp_wiki_search}") + url_to_extract_from = f"FEHLER bei Suche (SerpAPI): {str(e_serp_wiki_search)[:50]}..." + + # --- Datenextraktion, wenn eine URL bestimmt wurde --- + if url_to_extract_from and isinstance(url_to_extract_from, str) and url_to_extract_from.lower() not in ['kein artikel gefunden'] and not url_to_extract_from.startswith("FEHLER"): + self.logger.debug(f" -> Extrahiere Daten von URL: {url_to_extract_from[:100]}...") + try: + extracted_data = self.wiki_scraper.extract_company_data(url_to_extract_from) + if extracted_data and isinstance(extracted_data, dict) and extracted_data.get('url') != 'k.A.': + final_wiki_data = extracted_data + wiki_data_updated_in_this_run = True + self.logger.info(f" -> Datenextraktion von {url_to_extract_from[:100]}... erfolgreich.") + else: + self.logger.error(f" -> Fehler bei Datenextraktion von {url_to_extract_from[:100]}... oder Extraktion war leer. Setze Daten auf 'k.A.'") + final_wiki_data = {'url': url_to_extract_from, 'sitz_stadt': 'k.A.', 'sitz_land': 'k.A.', 'first_paragraph': 'k.A. (Extraktion fehlgeschlagen)', 'branche': 'k.A.', 'umsatz': 'k.A.', 'mitarbeiter': 'k.A.', 'categories': 'k.A.'} + wiki_data_updated_in_this_run = True except Exception as e_wiki_extract: - # Wenn extract_company_data eine Exception wirft (nach Retries) - self.logger.error(f"FEHLER bei Wikipedia Datenextraktion von {url_to_extract[:100]}...: {e_wiki_extract}") # <<< GEÄNDERT - # Setze Daten auf k.A., behalte aber die URL, von der extrahiert werden sollte - final_wiki_data = {'url': url_to_extract, 'first_paragraph': f'k.A. (FEHLER Extraktion: {str(e_wiki_extract)[:50]}...)', 'branche': 'k.A.', 'umsatz': 'k.A.', 'mitarbeiter': 'k.A.', 'categories': 'k.A.'} # Korrektur: e -> e_wiki_extract - wiki_data_updated_in_this_run = True # Markieren, dass die Daten ueberschrieben werden. - pass # Faert fort + self.logger.error(f"FEHLER bei Wikipedia Datenextraktion von {url_to_extract_from[:100]}...: {e_wiki_extract}") + final_wiki_data = {'url': url_to_extract_from, 'sitz_stadt': 'k.A.', 'sitz_land': 'k.A.', 'first_paragraph': f'k.A. (FEHLER Extraktion: {str(e_wiki_extract)[:50]}...)', 'branche': 'k.A.', 'umsatz': 'k.A.', 'mitarbeiter': 'k.A.', 'categories': 'k.A.'} + wiki_data_updated_in_this_run = True + else: # Wenn keine gueltige URL zum Extrahieren bestimmt wurde + self.logger.debug(f" -> Keine gueltige URL zum Extrahieren bestimmt ('{url_to_extract_from}'). Wiki-Daten nicht extrahiert oder bleiben auf Fehlerstatus.") + if url_to_extract_from in ['Kein Artikel gefunden'] or (isinstance(url_to_extract_from, str) and url_to_extract_from.startswith("FEHLER")): + final_wiki_data['url'] = url_to_extract_from + for key_to_clear in ['sitz_stadt', 'sitz_land', 'first_paragraph', 'branche', 'umsatz', 'mitarbeiter', 'categories']: + final_wiki_data[key_to_clear] = 'k.A.' + wiki_data_updated_in_this_run = True - else: - # Wenn keine gueltige URL zum Extrahieren bestimmt wurde (z.B. Suche fand nichts oder Fehler bei Suche) - self.logger.debug(f" -> Keine gueltige URL zum Extrahieren bestimmt ('{url_to_extract}'). Wiki-Daten nicht extrahiert.") # <<< GEÄNDERT - # final_wiki_data behaelt die current_wiki_data Werte (initial geladen) oder wurde oben bei Suche auf "Kein Artikel gefunden"/"FEHLER" gesetzt. - # Stelle sicher, dass final_wiki_data die richtige URL enthaelt, auch wenn keine Extraktion stattfand. - if url_to_extract in ['Kein Artikel gefunden'] or (isinstance(url_to_extract, str) and url_to_extract.startswith("FEHLER")): # Korrektur Pruefung - final_wiki_data['url'] = url_to_extract # Update nur die URL im Ergebnis - - # --- Sheet Updates fuer M-R und AN --- - # Diese Updates werden immer hinzugefuegt, WENN der WIKI-Schritt lief (run_wiki_step and wiki_processing_needed_based_on_status war True). - # Auch wenn die Suche/Extraktion fehlschlug (dann werden k.A. oder Fehlermeldungen geschrieben). - # Aktualisiere die Spalten M-R mit den finalen Daten im final_wiki_data Dictionary. + # --- Sheet Updates für N-AB (Wiki-Daten) und Timestamps V, W, X --- + # Verwende die NEUEN Spaltenschlüssel aus der aktuellen COLUMN_MAP updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Wiki URL"] + 1)}{row_num_in_sheet}', 'values': [[final_wiki_data.get('url', 'k.A.')]]}) + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Wiki Sitz Stadt"] + 1)}{row_num_in_sheet}', 'values': [[final_wiki_data.get('sitz_stadt', 'k.A.')]]}) + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Wiki Sitz Land"] + 1)}{row_num_in_sheet}', 'values': [[final_wiki_data.get('sitz_land', 'k.A.')]]}) updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Wiki Absatz"] + 1)}{row_num_in_sheet}', 'values': [[final_wiki_data.get('first_paragraph', 'k.A.')]]}) updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Wiki Branche"] + 1)}{row_num_in_sheet}', 'values': [[final_wiki_data.get('branche', 'k.A.')]]}) updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Wiki Umsatz"] + 1)}{row_num_in_sheet}', 'values': [[final_wiki_data.get('umsatz', 'k.A.')]]}) updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Wiki Mitarbeiter"] + 1)}{row_num_in_sheet}', 'values': [[final_wiki_data.get('mitarbeiter', 'k.A.')]]}) updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Wiki Kategorien"] + 1)}{row_num_in_sheet}', 'values': [[final_wiki_data.get('categories', 'k.A.')]]}) + + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Wikipedia Timestamp"] + 1)}{row_num_in_sheet}', 'values': [[now_timestamp]]}) # Spalte V - # Setze den Wikipedia Timestamp (AN), da der Wiki-Schritt lief (auch wenn fehlerhaft) - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Wikipedia Timestamp"] + 1)}{row_num_in_sheet}', 'values': [[now_timestamp]]}) + # Setze Y (Chat Wiki Konsistenzpruefung) und W (Wiki Verif. Timestamp) zurück, wenn Neubewertung nötig + status_y_from_sheet = self._get_cell_value_safe(row_data, "Chat Wiki Konsistenzpruefung").strip().upper() # Neuer Schlüssel Y + url_changed_and_is_valid_wiki_link = (url_in_n_sheet != final_wiki_data.get('url')) and \ + isinstance(final_wiki_data.get('url'), str) and \ + "wikipedia.org/wiki/" in final_wiki_data.get('url', '').lower() and \ + final_wiki_data.get('url', '').lower() not in ["k.a.", "kein artikel gefunden"] and \ + not final_wiki_data.get('url', '').startswith("FEHLER") - - # --- Setze S ('Chat Wiki Konsistenzpruefung') und AX ('Wiki Verif. Timestamp') zurueck, wenn Neubewertung noetig ist --- - # Eine Neubewertung (Zuruecksetzen von S und AX) ist noetig, wenn: - # - force_reeval True ist (immer bei Re-Eval des Wiki-Schritts) - # - Status S zuvor "X (URL Copied)" war (der Trigger fuer die Re-Extraktion) - # - Die neue URL in M (final_wiki_data['url']) anders ist als die urspruengliche URL aus M (url_in_m), UND die neue URL gueltig ist. - - status_s_indicates_reparse = self._get_cell_value_safe(row_data, "Chat Wiki Konsistenzpruefung").strip().upper() == "X (URL COPIED)" - # Pruefe, ob die FINAL_wiki_data URL (nach Suche/Extraktion) anders ist als die URSPRUENGLICHE URL in M im Sheet. - # UND stelle sicher, dass die neue URL eine gueltige URL ist (nicht "k.A." oder Fehlerstring). - new_wiki_url = final_wiki_data.get('url') - url_changed_and_valid = (url_in_m != new_wiki_url) and isinstance(new_wiki_url, str) and new_wiki_url.lower() not in ["k.a.", "kein artikel gefunden"] and not new_wiki_url.startswith("FEHLER") # Korrektur Pruefung - - # Bestimme, ob S und AX zurueckgesetzt werden sollen - if force_reeval or status_s_indicates_reparse or url_changed_and_valid: - s_idx = COLUMN_MAP.get("Chat Wiki Konsistenzpruefung") - ax_idx = COLUMN_MAP.get("Wiki Verif. Timestamp") - if s_idx is not None and ax_idx is not None: - s_let = self.sheet_handler._get_col_letter(s_idx + 1) - ax_let = self.sheet_handler._get_col_letter(ax_idx + 1) - - # Fuegen Sie die Updates zum Zuruecksetzen von S und AX hinzu - # S wird auf '?' gesetzt, um anzuzeigen, dass eine Verifizierung aussteht - updates.append({'range': f'{s_let}{row_num_in_sheet}', 'values': [["?"]]}) - # AX wird geleert, um die Batch-Verifizierung zu triggern - updates.append({'range': f'{ax_let}{row_num_in_sheet}', 'values': [[""]]}) - - # Bestimme den Grund-String fuer das Logging - grund_message_parts = [] - if force_reeval: grund_message_parts.append('Re-Eval') - if status_s_indicates_reparse: grund_message_parts.append("S='X (URL Copied)'") - if url_changed_and_valid: grund_message_parts.append('URL geaendert und gueltig') - grund_message_s_reset = ", ".join(grund_message_parts) - - self.logger.info(f" -> Status S zurueckgesetzt auf '?' und Timestamp AX geleert fuer erneute Verifikation (Grund: {grund_message_s_reset}).") # <<< GEÄNDERT + if force_reeval or status_y_from_sheet == "X (URL COPIED)" or url_changed_and_is_valid_wiki_link: + y_idx = COLUMN_MAP.get("Chat Wiki Konsistenzpruefung") + w_idx = COLUMN_MAP.get("Wiki Verif. Timestamp") + if y_idx is not None and w_idx is not None: + y_let = self.sheet_handler._get_col_letter(y_idx + 1) + w_let = self.sheet_handler._get_col_letter(w_idx + 1) + updates.append({'range': f'{y_let}{row_num_in_sheet}', 'values': [["?"]]}) + updates.append({'range': f'{w_let}{row_num_in_sheet}', 'values': [[""]]}) # W (alt AX) leeren + # Grund für Reset loggen + reset_reason_parts = [] + if force_reeval: reset_reason_parts.append('Re-Eval') + if status_y_from_sheet == "X (URL COPIED)": reset_reason_parts.append("Y='X (URL Copied)'") + if url_changed_and_is_valid_wiki_link: reset_reason_parts.append('URL geändert und valide') + self.logger.info(f" -> Status Y ('Chat Wiki Konsistenzpruefung') zurueckgesetzt auf '?' und Timestamp W ('Wiki Verif. Timestamp') geleert (Grund: {', '.join(reset_reason_parts) or 'Unbekannt'}).") else: - # Logge Fehler, wenn Spaltenindizes fehlen - self.logger.error("FEHLER: Konnte Spaltenbuchstaben fuer S oder AX nicht ermitteln. Zuruecksetzen uebersprungen.") # <<< GEÄNDERT + self.logger.error("FEHLER: Konnte Spaltenbuchstaben fuer Y oder W nicht ermitteln. Zuruecksetzen uebersprungen.") + + # else if run_wiki_step (aber nicht processing_needed_based_on_status): + # self.logger.debug(f"Zeile {row_num_in_sheet}: Ueberspringe WIKI Suche/Extraktion (Timestamp V vorhanden, Y nicht 'X (URL Copied)' und kein Re-Eval).") + + # --- Ende Wikipedia Handling --- # else if run_wiki_step: @@ -4962,47 +4884,33 @@ class DataProcessor: # Verarbeitung der markierten Zeilen - processed_count = 0 # Zaehlt Zeilen, fuer die _process_single_row aufgerufen wurde (im Rahmen des Limits). - # Die Liste updates_clear_flag wird NICHT mehr hier gefuellt, da _process_single_row das Update selbst hinzufuegt (Block 21). - # Die Liste rows_actually_processed wird nicht mehr benoetigt. - + processed_count = 0 + # Iteriere ueber die gefundenen markierten Zeilen for task in rows_to_process: - # Ueberpruefen Sie das Limit fuer die zu verarbeitenden Zeilen VOR der Verarbeitung + # === HIER DIE DEBUG-AUSGABE EINFÜGEN === + self.logger.debug(f"Re-Eval Loop Check: processed_count={processed_count}, row_limit={row_limit}") + # === ENDE DEBUG-AUSGABE === + if row_limit is not None and isinstance(row_limit, int) and row_limit > 0 and processed_count >= row_limit: - # Wenn das Limit erreicht ist und es ein positives Limit gibt - self.logger.info(f"Zeilenlimit ({row_limit}) fuer Re-Evaluation erreicht. Breche weitere Verarbeitung ab.") # <<< GEÄNDERT - break # Brich die Schleife ab + self.logger.info(f"Zeilenlimit ({row_limit}) fuer Re-Evaluation erreicht. Breche weitere Verarbeitung ab. Processed: {processed_count}") + break + row_num = task['row_num'] + row_data = task['data'] - row_num = task['row_num'] # 1-basierte Zeilennummer - row_data = task['data'] # Die Rohdaten fuer diese Zeile - - self.logger.info(f"Bearbeite Re-Eval Zeile {row_num}...") # <<< GEÄNDERT + self.logger.info(f"Bearbeite Re-Eval Zeile {row_num}...") try: - # Rufe die Methode zur Verarbeitung einer einzelnen Zeile auf (_process_single_row Block 19). - # In diesem Modus setzen wir immer force_reeval=True. - # Wir uebergeben die aus CLI/Menue ausgewaehlten Schritte in steps_to_run_set. - # Wir uebergeben das clear_flag, damit _process_single_row weiss, ob das 'x' geloescht werden soll. - # _process_single_row fuehrt die Schritte durch, sammelt Updates (inkl. 'x'-Flag Update wenn clear_x_flag=True) - # und sendet das Batch-Update fuer diese Zeile. self._process_single_row( row_num_in_sheet = row_num, row_data = row_data, - steps_to_run = steps_to_run_set, # <-- Uebergibt die aus CLI/Menue ausgewaehlten Schritte - force_reeval = True, # <-- Erzwingt Re-Evaluation unabhaengig von Timestamps fuer die ausgewaehlten Schritte - clear_x_flag = clear_flag # <-- Uebergibt, ob das 'x'-Flag von _process_single_row geloescht werden soll + steps_to_run = steps_to_run_set, + force_reeval = True, + clear_x_flag = clear_flag ) - - # Zaehlen, wenn _process_single_row erfolgreich aufgerufen wurde (unabhaengig von internen Ueberspringungen in _process_single_row). - processed_count += 1 - # Die Liste rows_actually_processed wird nicht mehr benoetigt. - + processed_count += 1 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. - # Das 'x'-Flag wird in diesem Fall NICHT geloescht, da _process_single_row nicht bis zum Ende kam. - self.logger.exception(f"FEHLER bei Re-Evaluation von Zeile {row_num}: {e_proc}") # <<< GEÄNDERT + self.logger.exception(f"FEHLER bei Re-Evaluation von Zeile {row_num}: {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.