diff --git a/brancheneinstufung.py b/brancheneinstufung.py index 1f3cbfdc..dc1be8c4 100644 --- a/brancheneinstufung.py +++ b/brancheneinstufung.py @@ -4349,211 +4349,143 @@ class DataProcessor: in Spalte A geloescht, wenn die Zeile verarbeitet wurde. Defaults to False. """ - # Verwenden Sie logger, da das Logging jetzt konfiguriert ist - self.logger.info(f"--- Starte Verarbeitung fuer Zeile {row_num_in_sheet} {'(Re-Eval)' if force_reeval else ''} (Schritte: {', '.join(steps_to_run) if steps_to_run else 'Keine ausgewählt'}) ---") # <<< GEÄNDERT - - # Liste zur Sammlung von Sheet-Updates fuer diese Zeile - # Updates sind Dictionaries: {'range': 'A1', 'values': [['Wert']]} + self.logger.info(f"--- Starte Verarbeitung fuer Zeile {row_num_in_sheet} {'(Re-Eval)' if force_reeval else ''} (Schritte: {', '.join(steps_to_run) if steps_to_run else 'Keine ausgewählt'}) ---") updates = [] - - # Aktueller Zeitstempel fuer die Timestamp-Spalten now_timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - # Flag, ob IRGENDEINE Verarbeitung in dieser Zeile stattgefunden hat any_processing_done = False - - # Flags, die den Zustand der Zeile waehrend DIESES Laufs verfolgen - wiki_data_updated_in_this_run = False # Wurden Wiki-Daten (M-R, AN) gerade aktualisiert? (Trigger fuer Chat) - chat_eval_just_ran = False # Liefen die ChatGPT-Evaluationen (AO) gerade? (Trigger fuer ML) - + wiki_data_updated_in_this_run = False + chat_eval_just_ran = False # --- Initiale Werte lesen --- - # Lesen Sie die Werte aus den Spalten, die fuer mehrere Schritte benoetigt werden. - # Nutzt die interne Helferfunktion _get_cell_value_safe, um sicher auf Zellen zuzugreifen. - # Stellen Sie sicher, dass alle benoetigten Spalten in COLUMN_MAP (Block 1) vorhanden sind. - - # CRM Daten company_name = self._get_cell_value_safe(row_data, "CRM Name").strip() - website_url = self._get_cell_value_safe(row_data, "CRM Website").strip() # Arbeitskopie der URL (kann sich aendern) - original_website_url_in_sheet = website_url # Originalwert aus Sheet behalten, fuer Website Lookup Logik - - crm_kurzform = self._get_cell_value_safe(row_data, "CRM Kurzform").strip() # Benoetigt fuer Contact Search + website_url = self._get_cell_value_safe(row_data, "CRM Website").strip() + original_website_url_in_sheet = website_url + crm_kurzform = self._get_cell_value_safe(row_data, "CRM Kurzform").strip() crm_branche = self._get_cell_value_safe(row_data, "CRM Branche").strip() crm_beschreibung = self._get_cell_value_safe(row_data, "CRM Beschreibung").strip() - # CRM Umsatz und Mitarbeiter werden direkt aus row_data geholt, wenn benoetigt (z.B. fuer Konsolidierung) + + # NEU: Wert aus Spalte D (Parent Account Name) hier lesen + parent_account_name_d = self._get_cell_value_safe(row_data, "Parent Account Name").strip() + self.logger.debug(f" Zeile {row_num_in_sheet}: Gelesener Parent Account (D): '{parent_account_name_d}'") - - # Vorhandene Wikipedia-Daten (aus dem Sheet) - # Dies sind die Daten, die am ANFANG im Sheet stehen. Sie werden im Wiki-Schritt (Block 21) - # durch extrahierte Daten ersetzt, wenn der Wiki-Schritt laeuft. - # Initialisiere mit "k.A." wenn die Zelle leer ist. + # Vorgeschlagener Parent aus Spalte O (wird für Wiki-Suche und Parent-Vorschlagslogik benötigt) + system_suggested_parent_name_o = self._get_cell_value_safe(row_data, "System Vorschlag Parent Account").strip() # Spalte O + current_wiki_data = { 'url': self._get_cell_value_safe(row_data, "Wiki URL") or 'k.A.', + 'sitz_stadt': self._get_cell_value_safe(row_data, "Wiki Sitz Stadt") or 'k.A.', + 'sitz_land': self._get_cell_value_safe(row_data, "Wiki Sitz Land") or 'k.A.', 'first_paragraph': self._get_cell_value_safe(row_data, "Wiki Absatz") or 'k.A.', 'branche': self._get_cell_value_safe(row_data, "Wiki Branche") or 'k.A.', 'umsatz': self._get_cell_value_safe(row_data, "Wiki Umsatz") or 'k.A.', 'mitarbeiter': self._get_cell_value_safe(row_data, "Wiki Mitarbeiter") or 'k.A.', 'categories': self._get_cell_value_safe(row_data, "Wiki Kategorien") or 'k.A.' } - # Arbeitskopie fuer die Wiki-Daten. Wird im Wiki-Schritt aktualisiert. - # Die Chat-Evaluationen (Block 22) und ML-Schaetzung (Block 23) nutzen diese finalen Daten. final_wiki_data = current_wiki_data.copy() + website_raw = self._get_cell_value_safe(row_data, "Website Rohtext") or 'k.A.' + website_summary = self._get_cell_value_safe(row_data, "Website Zusammenfassung") or 'k.A.' + website_meta_details = self._get_cell_value_safe(row_data, "Website Meta-Details") or 'k.A.' # Spalte AI + url_pruefstatus = self._get_cell_value_safe(row_data, "URL Prüfstatus") or '' # Spalte AK - # Vorhandener Website-Rohtext und Zusammenfassung (aus dem Sheet) - # Dies sind die Daten, die am ANFANG im Sheet stehen. Sie werden im Website-Schritt (Block 20) - # durch gescrapte Daten ersetzt, wenn der Website-Schritt laeuft. - # Initialisiere mit "k.A." wenn die Zelle leer ist. - current_website_raw = self._get_cell_value_safe(row_data, "Website Rohtext") or 'k.A.' - current_website_summary = self._get_cell_value_safe(row_data, "Website Zusammenfassung") or 'k.A.' - # Arbeitskopien fuer die Website-Daten. Werden im Website-Schritt aktualisiert. - # Die Chat-Evaluationen (Block 22) und ML-Schaetzung (Block 23) nutzen diese finalen Daten. - website_raw = current_website_raw # Kann im Website-Schritt aktualisiert werden - website_summary = current_website_summary # Kann im Website-Schritt aktualisiert werden - - - # --- Die Logik fuer die einzelnen Verarbeitungsschritte folgt in den naechsten Bloecken --- - # Jeder Schritt prueft, ob er in steps_to_run enthalten ist UND (ob er laut Status noetig ist ODER force_reeval True ist). - - # ====================================================================== - # === Verarbeitungsschritte innerhalb von _process_single_row ========== - # ====================================================================== - - # --- 1. Website Handling (Lookup, Scraping, Summarization) --- - # Dieser Schritt wird ausgefuehrt, wenn 'web' in steps_to_run enthalten ist UND - # (_needs_website_processing True ist ODER force_reeval True ist). - # _needs_website_processing prueft AT. Die Lookup-Logik (D leer) ist separat. - # Die Website-Verarbeitung umfasst Lookup (optional), Scraping und Summarization. - # Nutzt interne Helfer: _get_cell_value_safe, _needs_website_processing. - # Nutzt globale Helfer: COLUMN_MAP, logger, serp_website_lookup, get_website_raw, summarize_website_content, datetime, time. - - # Pruefen Sie, ob der Website-Schritt im aktuellen Lauf angefordert wurde + # --- 1. Website Handling (Lookup, Scraping, Summarization, Meta) --- run_website_step = 'web' in steps_to_run - # Pruefen Sie, ob der Website-Schritt laut Status oder Re-Eval noetig ist website_processing_needed_based_on_status = self._needs_website_processing(row_data, force_reeval) - # Wenn der Website-Schritt angefordert wurde UND laut Status/Re-Eval noetig ist if run_website_step and website_processing_needed_based_on_status: - any_processing_done = True # Markiere, dass in dieser Zeile etwas getan wird + any_processing_done = True + grund_message_parts_web = [] + if force_reeval: grund_message_parts_web.append('Re-Eval') + if not self._get_cell_value_safe(row_data, "Website Scrape Timestamp").strip(): grund_message_parts_web.append('AJ (Website Scrape Timestamp) leer') # Angepasster Key + grund_message_web = ", ".join(filter(None, grund_message_parts_web)) or "Unbekannter Grund" + self.logger.info(f"Zeile {row_num_in_sheet}: Fuehre WEBSITE Schritte aus (Grund: {grund_message_web})...") - # Bestimme den Grund fuer die Ausfuehrung dieses Schritts fuer das Logging - grund_message_parts = [] - if force_reeval: grund_message_parts.append('Re-Eval') - if not self._get_cell_value_safe(row_data, "Website Scrape Timestamp").strip(): grund_message_parts.append('AT leer') - grund_message = ", ".join(grund_message_parts) - - self.logger.info(f"Zeile {row_num_in_sheet}: Fuehre WEBSITE Schritte aus (Grund: {grund_message})...") # <<< GEÄNDERT - - # Website Lookup nur, wenn die URL in Spalte D (CRM Website) leer oder "k.A." ist - # Nutzt die lokal gespeicherte Kopie der URL, die ggf. im Lookup ueberschrieben wird. + # a) Website Lookup (nur wenn D leer) if not website_url or website_url.lower() == "k.a.": - self.logger.debug(" -> Website URL (D) leer oder k.A., suche ueber SERP...") # <<< GEÄNDERT - # Annahme: serp_website_lookup global definiert (Block 10) und nutzt logging/retry - try: - # Der serp_website_lookup Aufruf ist mit retry_on_failure dekoriert. - # Wenn er nach Retries fehlschlaegt, wirft er eine Exception. - new_website = serp_website_lookup(company_name) # Nutzt globalen Helfer (Block 10) - # Wenn eine neue Website gefunden wurde - if new_website != "k.A.": - # Ueberschreibe die lokale Variable website_url fuer den weiteren Schritt (Scraping) - website_url = new_website - # Fuegen Sie das Update fuer Spalte D zur Liste der Sheet-Updates hinzu - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["CRM Website"] + 1)}{row_num_in_sheet}', 'values': [[website_url]]}) - self.logger.info(f" -> Neue Website gefunden und fuer Update D:{row_num_in_sheet} vorgemerkt: {website_url[:100]}...") # <<< GEÄNDERT - else: - # Wenn keine neue Website gefunden wurde - self.logger.warning(f" -> Keine neue Website ueber SERP gefunden fuer '{company_name[:100]}...'.") # <<< GEÄNDERT - # website_url bleibt leer oder k.A. in diesem Fall. + self.logger.debug(" -> Website URL (E) leer oder k.A., suche ueber SERP...") + try: + new_website = serp_website_lookup(company_name) + if new_website and new_website.lower() != "k.a." and not new_website.startswith("k.A. (Fehler"): + website_url = new_website # Aktualisiere Arbeitskopie + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["CRM Website"] + 1)}{row_num_in_sheet}', 'values': [[website_url]]}) + self.logger.info(f" -> Neue Website gefunden und für Update E:{row_num_in_sheet} vorgemerkt: {website_url[:100]}...") + url_pruefstatus = "URL_OK_SERP" # Status aktualisieren + else: + self.logger.warning(f" -> Keine neue Website ueber SERP gefunden für '{company_name[:100]}...'.") + url_pruefstatus = "URL_SERP_FAILED" if not website_url or website_url.lower() == "k.a." else url_pruefstatus # Nur setzen, wenn vorher keine URL da war + except Exception as e_serp_lookup_web: + self.logger.error(f"FEHLER bei SERP Website Lookup für '{company_name[:100]}...': {e_serp_lookup_web}") + url_pruefstatus = "URL_SERP_ERROR" - except Exception as e_serp_lookup: - # Wenn serp_website_lookup eine Exception wirft (nach Retries) - self.logger.error(f"FEHLER bei SERP Website Lookup fuer '{company_name[:100]}...': {e_serp_lookup}") # <<< GEÄNDERT - # Bei Fehler bleibt website_url unveraendert (leer oder k.A.). Fahren Sie fort. - pass # Fahren Sie fort, falls eine URL im Sheet war oder gefunden wurde - - # Führen Sie Scraping und Zusammenfassung nur durch, wenn eine gueltige Website URL vorhanden ist (lokale Variable website_url) - # Ueberpruefen Sie auf nicht-leere website_url und ungleich "k.A." oder Fehlerwerten. + # b) Scrape Rohtext, Meta-Details und c) Zusammenfassung (nur wenn URL vorhanden) if website_url and website_url.lower() not in ["k.a.", "kein artikel gefunden", "fehler bei suche", "http:"]: - self.logger.debug(f" -> Scrape Rohtext von {website_url[:100]}...") # <<< GEÄNDERT - # Annahme: get_website_raw global definiert (Block 11) und nutzt logging/retry - try: - # Der get_website_raw Aufruf ist mit retry_on_failure dekoriert. - # Wenn er nach Retries fehlschlaegt, wirft er eine Exception oder gibt einen Fehlerwert zurueck. - new_website_raw = get_website_raw(website_url) # Nutzt globalen Helfer (Block 11) - website_raw = new_website_raw # Aktualisiere die lokale Variable (AR Wert) + self.logger.debug(f" -> Scrape Rohtext & Meta von {website_url[:100]}...") + new_website_raw_temp = "k.A." # Temporär für diesen Scope + try: + new_website_raw_temp = get_website_raw(website_url) # Nutzt globale Funktion + website_raw = new_website_raw_temp # Update der Hauptvariable + + # Wenn get_website_raw einen URL_CHECK_MARKER zurückgibt + if website_raw == URL_CHECK_MARKER: + url_pruefstatus = URL_CHECK_MARKER + self.logger.warning(f" -> get_website_raw markierte URL {website_url} als '{URL_CHECK_MARKER}'. Überspringe weitere Web-Verarbeitung.") + website_summary = "k.A. (URL prüfen)" # Verhindere alte Zusammenfassung + website_meta_details = "k.A. (URL prüfen)" + elif website_raw and str(website_raw).strip().lower() not in ["k.a.", "k.a. (nur cookie-banner erkannt)", "k.a. (fehler)"]: + url_pruefstatus = "URL_OK_SCRAPED" # Wenn Scraping (auch nur teilweise) erfolgreich war + + # Meta-Details scrapen (experimentell) + try: + meta_temp = scrape_website_details(website_url) + website_meta_details = meta_temp if meta_temp else "k.A. (Keine Meta-Details)" + except Exception as e_meta: + self.logger.error(f"FEHLER bei Meta-Detail Scraping für '{website_url[:100]}...': {e_meta}") + website_meta_details = f"k.A. (Fehler Meta: {str(e_meta)[:50]})" + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Meta-Details"] + 1)}{row_num_in_sheet}', 'values': [[website_meta_details]]}) # Spalte AI - # Zusammenfassung nur, wenn gueltiger Rohtext extrahiert wurde. - # Pruefen Sie auf nicht-leeren raw_text und ungleich Standard-Fehlerwerten. - if website_raw and str(website_raw).strip().lower() not in ["k.a.", "k.a. (nur cookie-banner erkannt)", "k.a. (fehler)"]: - self.logger.debug(f" -> Fasse Rohtext zusammen (Laenge: {len(str(website_raw))})...") # <<< GEÄNDERT - # Annahme: summarize_website_content global definiert (Block 9) und nutzt logging/retry - try: - # Der summarize_website_content Aufruf ist mit retry_on_failure dekoriert. - # Wenn er nach Retries fehlschlaegt, wirft er eine Exception oder gibt einen Fehlerwert zurueck. - new_website_summary = summarize_website_content(website_raw) # Nutzt globalen Helfer (Block 9) - # Aktualisiere die lokale Variable (AS Wert). Wenn das Ergebnis leer ist, setze "k.A.". - website_summary = new_website_summary if new_website_summary and new_website_summary.strip() else "k.A. (Keine Zusammenfassung erhalten)" - # Fuegen Sie das Update fuer Spalte AS (Website Zusammenfassung) zur Liste hinzu - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Zusammenfassung"] + 1)}{row_num_in_sheet}', 'values': [[website_summary]]}) - - except Exception as e_summary: - # Wenn summarize_website_content eine Exception wirft (nach Retries) - self.logger.error(f"FEHLER bei Website Zusammenfassung fuer '{company_name[:100]}...': {e_summary}") # <<< GEÄNDERT - # Setze die lokale Variable auf einen Fehlerwert - website_summary = f"k.A. (Fehler Zusammenfassung: {str(e_summary)[:100]}...)" # Korrektur: e statt e_summary - # Fuegen Sie ein Update mit dem Fehlerwert fuer Spalte AS hinzu - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Zusammenfassung"] + 1)}{row_num_in_sheet}', 'values': [[website_summary]]}) - pass # Fahren Sie fort - - else: - # Wenn kein gueltiger Rohtext zum Zusammenfassen vorhanden war - self.logger.debug(" -> Kein gueltiger Rohtext zum Zusammenfassen vorhanden. Zusammenfassung uebersprungen.") # <<< GEÄNDERT - # Stellen Sie sicher, dass die lokale Variable korrekt gesetzt ist, falls nicht zusammengefasst - website_summary = "k.A." - # Fuege 'k.A.' Update fuer AS hinzu (nur wenn es vorher nicht k.A. war?) - # Oder immer setzen, wenn der Schritt lief und keine Zusammenfassung erstellt wurde. - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Zusammenfassung"] + 1)}{row_num_in_sheet}', 'values': [['k.A.']]}) + # Zusammenfassung + self.logger.debug(f" -> Fasse Rohtext zusammen (Laenge: {len(str(website_raw))})...") + try: + summary_temp = summarize_website_content(website_raw) + website_summary = summary_temp if summary_temp and summary_temp.strip() else "k.A. (Keine Zusammenfassung erhalten)" + except Exception as e_summary_web: + self.logger.error(f"FEHLER bei Website Zusammenfassung für '{company_name[:100]}...': {e_summary_web}") + website_summary = f"k.A. (Fehler Zusammenfassung: {str(e_summary_web)[:50]})" + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Zusammenfassung"] + 1)}{row_num_in_sheet}', 'values': [[website_summary]]}) # Spalte AH + else: # Fehler beim Scraping oder kein sinnvoller Text + if not website_raw.startswith("k.A. (Fehler"): # Wenn es kein expliziter Fehler war, sondern z.B. nur Cookie-Banner + url_pruefstatus = "URL_SCRAPE_EMPTY_OR_BANNER" + # website_raw enthält bereits den Fehler oder "k.A. (nur Cookie...)" + website_summary = "k.A." # Keine Zusammenfassung bei fehlerhaftem/leerem Rohtext + website_meta_details = "k.A." + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Zusammenfassung"] + 1)}{row_num_in_sheet}', 'values': [[website_summary]]}) + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Meta-Details"] + 1)}{row_num_in_sheet}', 'values': [[website_meta_details]]}) - # Fuegen Sie das Update fuer Spalte AR (Website Rohtext) zur Liste hinzu (auch wenn es ein Fehlerwert ist) - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Rohtext"] + 1)}{row_num_in_sheet}', 'values': [[website_raw]]}) - - except Exception as e_scrape: - # Wenn get_website_raw eine Exception wirft (nach Retries) - self.logger.error(f"FEHLER beim Website Scraping fuer '{company_name[:100]}' ({website_url[:100]}...): {e_scrape}") # <<< GEÄNDERT - # Setze die lokalen Variablen auf Fehlerwerte - website_raw = f"k.A. (Fehler Scraping: {str(e_scrape)[:100]}...)" # Korrektur: e statt e_scrape - website_summary = "k.A. (Fehler Zusammenfassung)" # Zusammenfassung fehlschlaegt auch - # Fuegen Sie Updates mit Fehlerwerten fuer AR und AS hinzu - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Rohtext"] + 1)}{row_num_in_sheet}', 'values': [[website_raw]]}) - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Zusammenfassung"] + 1)}{row_num_in_sheet}', 'values': [[website_summary]]}) - pass # Fahren Sie fort - - else: - # Wenn keine gueltige Website URL vorhanden/gefunden wurde - self.logger.debug(f" -> Keine gueltige Website URL vorhanden/gefunden fuer '{company_name[:100]}...'. Website Verarbeitung uebersprungen.") # <<< GEÄNDERT - # Stellen Sie sicher, dass AR und AS auf k.A. gesetzt werden, wenn der Schritt lief, aber keine URL da war. - # Die lokalen Variablen behalten ihre initialen Werte (current_...) wenn der Schritt uebersprungen wurde, - # aber wenn der Schritt lief, aber keine URL da war, sollten sie auf k.A. gesetzt werden. - # Setzen Sie explizit auf k.A. oder Fehlerwerte, falls der Schritt lief aber fehlschlug wegen URL. - website_raw = "k.A." - website_summary = "k.A." - # Fuegen Sie Updates fuer AR und AS hinzu, falls noetig (Vermeidung von doppelten k.A. Updates) - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Rohtext"] + 1)}{row_num_in_sheet}', 'values': [['k.A.']]}) - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Zusammenfassung"] + 1)}{row_num_in_sheet}', 'values': [['k.A.']]}) - - - # Setzen Sie den Website Scrape Timestamp (AT), da der Website-Schritt lief (auch wenn fehlerhaft) - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Scrape Timestamp"] + 1)}{row_num_in_sheet}', 'values': [[now_timestamp]]}) - - # else if run_website_step: - # Der Website Schritt war angefordert, aber nicht noetig basierend auf Status/Re-Eval. - # Die lokalen Variablen website_raw und website_summary behalten ihre initialen Werte (current_...). - # self.logger.debug(f"Zeile {row_num_in_sheet}: Ueberspringe WEBSITE Schritte (AT vorhanden und kein Re-Eval).") # Zu viel Laerm im Debug - - - # --- Der Code fuer den naechsten Verarbeitungsschritt (Wikipedia) folgt im naechsten Block --- - # Definition der Methode _process_single_row wird in der naechsten Nachricht fortgesetzt. + except Exception as e_scrape_web: + self.logger.error(f"FEHLER beim Website Scraping für '{company_name[:100]}' ({website_url[:100]}...): {e_scrape_web}") + website_raw = f"k.A. (Fehler Scraping: {str(e_scrape_web)[:50]})" + website_summary = "k.A. (Scraping fehlgeschlagen)" + website_meta_details = "k.A. (Scraping fehlgeschlagen)" + url_pruefstatus = "URL_SCRAPE_ERROR" + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Zusammenfassung"] + 1)}{row_num_in_sheet}', 'values': [[website_summary]]}) + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Meta-Details"] + 1)}{row_num_in_sheet}', 'values': [[website_meta_details]]}) + + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Rohtext"] + 1)}{row_num_in_sheet}', 'values': [[website_raw]]}) # Spalte AG + + else: # Keine gültige URL für Scraping + self.logger.debug(f" -> Keine gültige Website URL für '{company_name[:100]}...' vorhanden. Web-Verarbeitung übersprungen.") + website_raw = "k.A. (Keine URL)" + website_summary = "k.A." + website_meta_details = "k.A." + # Wenn url_pruefstatus noch leer ist (weil keine URL-Suche stattfand), setze es. + if not url_pruefstatus : url_pruefstatus = "URL_MISSING" + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Rohtext"] + 1)}{row_num_in_sheet}', 'values': [[website_raw]]}) + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Zusammenfassung"] + 1)}{row_num_in_sheet}', 'values': [[website_summary]]}) + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Meta-Details"] + 1)}{row_num_in_sheet}', 'values': [[website_meta_details]]}) + + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["URL Prüfstatus"] + 1)}{row_num_in_sheet}', 'values': [[url_pruefstatus]]}) # Spalte AK + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Scrape Timestamp"] + 1)}{row_num_in_sheet}', 'values': [[now_timestamp]]}) # Spalte AJ # ====================================================================== # === 2. Wikipedia Handling (Search, Extraction, Status Reset) ========== @@ -4742,92 +4674,309 @@ class DataProcessor: # Timestamp für Wikipedia-Extraktion (Z) immer setzen, wenn der Wiki-Schritt lief updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Wikipedia Timestamp"] + 1)}{row_num_in_sheet}', 'values': [[now_timestamp]]}) + # --- 2. Wikipedia Handling (Search, Extraction, Status Reset) ========== + run_wiki_step = 'wiki' in steps_to_run + wiki_processing_needed_based_on_status = self._needs_wiki_processing(row_data, force_reeval) # Prüft Z und AC - # Wenn eine SerpAPI-Suche für die Tochter durchgeführt wurde, setze den SerpAPI Wiki Search Timestamp (AB) - # Das Flag `search_for_daughter_wiki_needed` muss aus der Tochter-Logik kommen. - # if search_for_daughter_wiki_needed: - # updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["SerpAPI Wiki Search Timestamp"] + 1)}{row_num_in_sheet}', 'values': [[now_timestamp]]}) + if run_wiki_step and wiki_processing_needed_based_on_status: + any_processing_done = True + + grund_message_parts_wiki = [] # Für Logging des Grundes + if force_reeval: grund_message_parts_wiki.append('Re-Eval') + if not self._get_cell_value_safe(row_data, "Wikipedia Timestamp").strip(): grund_message_parts_wiki.append('Z (Wikipedia Timestamp) leer') + if self._get_cell_value_safe(row_data, "Chat Wiki Konsistenzpruefung").strip().upper() == "X (URL COPIED)": grund_message_parts_wiki.append("AC (Chat Wiki Konsistenzpruefung)='X (URL COPIED)'") + grund_message_wiki = ", ".join(filter(None, grund_message_parts_wiki)) or "Unbekannter Grund (Wiki)" + self.logger.info(f"Zeile {row_num_in_sheet}: Fuehre WIKI Schritte aus (Grund: {grund_message_wiki})...") - # Status AC (Chat Wiki Konsistenzpruefung) und AA (Wiki Verif. Timestamp) zurücksetzen, wenn Neubewertung nötig - # Dies ist insbesondere dann der Fall, wenn eine neue URL (egal ob von Parent oder Tochter) in R geschrieben wurde, - # oder wenn Re-Eval forciert wurde. - # Die Logik hierfür muss die `source_of_wiki_data_origin` und `url_to_extract_from` vs. `current_wiki_url_in_sheet_for_daughter` vergleichen. - - # Vereinfachte Reset-Logik: Wenn wiki_data_updated_in_this_run True ist UND der neue Status nicht "INFO_PARENT_WIKI" ist, - # dann setze AC auf "?" und leere AA. - ac_current_value = self._get_cell_value_safe(row_data, "Chat Wiki Konsistenzpruefung").strip() - - # Wenn eine neue URL gefunden und extrahiert wurde (egal ob Parent oder Tochter-Suche) - # ODER wenn Re-Eval war und eine URL vorhanden ist, von der extrahiert wurde. - reset_ac_aa = False - if wiki_data_updated_in_this_run and \ - final_wiki_data.get('url', 'k.A.').lower() not in ["k.a.", "kein artikel gefunden", "kein artikel gefunden (parent)"] and \ - not final_wiki_data.get('url', '').startswith("FEHLER"): + # Hole aktuellen Wiki-URL der Tochter aus Spalte R + current_wiki_url_in_sheet_for_daughter = self._get_cell_value_safe(row_data, "Wiki URL").strip() + + url_to_validate_and_extract = None # Die URL, die wir versuchen zu validieren und zu extrahieren + name_for_wiki_article_validation = company_name # Standard: Validiere gegen Tochter-Namen + source_of_wiki_data_origin = "Tochter" + additional_info_for_af_col = "" # Für Spalte AF "Begründung bei Abweichung" + + # --- START: LOGIK FÜR PARENT-ACCOUNT (O) UND WIKI-URL DER TOCHTER (R) --- + if system_suggested_parent_name_o and system_suggested_parent_name_o.lower() != "k.a.": + # Fall 1: Parent (O) ist vorgeschlagen. + if not current_wiki_url_in_sheet_for_daughter or current_wiki_url_in_sheet_for_daughter.lower() == "k.a.": + # Fall 1.1: O gefüllt, R (Tochter-Wiki) leer/k.A. -> Suche Wiki für Parent O + self.logger.info(f" Zeile {row_num_in_sheet}: Parent (O) '{system_suggested_parent_name_o}' vorhanden, Wiki-URL der Tochter (R) leer. Versuche Wiki-Suche für Parent via SerpAPI...") + try: + parent_url_from_serp = serp_wikipedia_lookup(system_suggested_parent_name_o, website=None) # Kein spezifischer Website-Kontext für Parent-Suche + if parent_url_from_serp: + url_to_validate_and_extract = parent_url_from_serp + name_for_wiki_article_validation = system_suggested_parent_name_o # Validierung gegen Parent-Namen + source_of_wiki_data_origin = "Parent (O via SerpAPI)" + self.logger.info(f" -> Wiki-Artikel für Parent '{system_suggested_parent_name_o}' via SerpAPI gefunden: {url_to_validate_and_extract}") + else: + self.logger.warning(f" -> Kein Wiki-Artikel für Parent '{system_suggested_parent_name_o}' via SerpAPI gefunden. Fahre fort mit Standard-Tochter-Suche (falls nötig).") + # url_to_validate_and_extract bleibt None, Standard-Tochter-Suche wird unten getriggert, wenn nötig. + except Exception as e_parent_wiki_search: + self.logger.error(f" -> Fehler bei SerpAPI-Suche nach Wiki für Parent '{system_suggested_parent_name_o}': {e_parent_wiki_search}") + # Standard-Tochter-Suche wird unten getriggert, wenn nötig. + else: + # Fall 1.2: O gefüllt, R (Tochter-Wiki) auch gefüllt. + self.logger.info(f" Zeile {row_num_in_sheet}: Parent (O) '{system_suggested_parent_name_o}' vorhanden, aber auch Wiki-URL der Tochter (R) '{current_wiki_url_in_sheet_for_daughter}'. Tochter-Wiki (R) wird primär für Validierung/Extraktion verwendet, falls nötig.") + url_to_validate_and_extract = current_wiki_url_in_sheet_for_daughter # Nutze die vorhandene Tochter-URL + name_for_wiki_article_validation = company_name # Validierung gegen Tochter-Namen + source_of_wiki_data_origin = "Tochter (aus R, Parent O ignoriert)" + # --- ENDE: LOGIK FÜR PARENT-ACCOUNT (O) UND WIKI-URL DER TOCHTER (R) --- + + # --- START: STANDARD-LOGIK FÜR TOCHTER-WIKI-SUCHE (falls noch keine URL von Parent O bestimmt wurde) --- + if url_to_validate_and_extract is None: # Nur wenn noch keine URL vom Parent oder aus R (Re-Eval) kam + search_for_daughter_wiki_needed = False + status_ac_is_reparse = self._get_cell_value_safe(row_data, "Chat Wiki Konsistenzpruefung").strip().upper() == "X (URL COPIED)" + timestamp_z_is_empty = not self._get_cell_value_safe(row_data, "Wikipedia Timestamp").strip() + r_url_is_valid_looking = current_wiki_url_in_sheet_for_daughter and \ + "wikipedia.org/wiki/" in current_wiki_url_in_sheet_for_daughter.lower() and \ + current_wiki_url_in_sheet_for_daughter.lower() not in ["k.a.", "kein artikel gefunden", "fehler bei suche", "http:"] + + if status_ac_is_reparse: + self.logger.info(f" Zeile {row_num_in_sheet}: Status AC ist 'X (URL COPIED)'. Starte neue Wiki-Suche für Tochter '{company_name}'.") + search_for_daughter_wiki_needed = True + elif force_reeval: # Wenn Re-Eval und keine Parent-URL gefunden wurde + if r_url_is_valid_looking: + self.logger.info(f" Zeile {row_num_in_sheet}: Re-Eval Modus. Nutze vorhandene Tochter-URL (R): {current_wiki_url_in_sheet_for_daughter}") + url_to_validate_and_extract = current_wiki_url_in_sheet_for_daughter + name_for_wiki_article_validation = company_name + source_of_wiki_data_origin = "Tochter (aus R, Re-Eval)" + else: + self.logger.info(f" Zeile {row_num_in_sheet}: Re-Eval Modus. Tochter-URL (R) leer/ungültig. Starte neue Wiki-Suche für Tochter '{company_name}'.") + search_for_daughter_wiki_needed = True + elif timestamp_z_is_empty: # Wikipedia Timestamp (Z) fehlt + if r_url_is_valid_looking: + self.logger.info(f" Zeile {row_num_in_sheet}: Wikipedia Timestamp (Z) fehlt. Validiere vorhandene Tochter-URL (R): {current_wiki_url_in_sheet_for_daughter}") + url_to_validate_and_extract = current_wiki_url_in_sheet_for_daughter + name_for_wiki_article_validation = company_name + source_of_wiki_data_origin = "Tochter (aus R, Z leer)" + else: + self.logger.info(f" Zeile {row_num_in_sheet}: Wikipedia Timestamp (Z) fehlt und Tochter-URL (R) leer/ungültig. Starte neue Wiki-Suche für Tochter '{company_name}'.") + search_for_daughter_wiki_needed = True + elif not r_url_is_valid_looking: # Fallback, wenn Z nicht leer, aber R schlecht ist + self.logger.info(f" Zeile {row_num_in_sheet}: Tochter-URL (R) ist ungültig ('{current_wiki_url_in_sheet_for_daughter}'). Starte neue Wiki-Suche für Tochter '{company_name}'.") + search_for_daughter_wiki_needed = True - if source_of_wiki_data_origin == "Parent (O)": - # AC wurde bereits auf "INFO_PARENT_WIKI" gesetzt, AA geleert. Hier nichts weiter tun. - pass - elif current_wiki_url_in_sheet_for_daughter != final_wiki_data.get('url'): # URL der Tochter hat sich geändert - reset_ac_aa = True - elif force_reeval: # Re-Eval und eine URL wurde verarbeitet - reset_ac_aa = True - elif status_ac_indicates_reparse: # AC war "X (URL COPIED)" - reset_ac_aa = True + if search_for_daughter_wiki_needed: + self.logger.info(f" -> Suche nach Wikipedia-Artikel für Tochter '{company_name}' (Standard-Suche)...") + daughter_page_object = self.wiki_scraper.search_company_article(company_name=company_name, website=website_url) + if daughter_page_object: + url_to_validate_and_extract = daughter_page_object.url + name_for_wiki_article_validation = company_name + source_of_wiki_data_origin = "Tochter (Standard-Suche)" + self.logger.info(f" -> Suche für Tochter '{company_name}' erfolgreich, gefundene URL: {url_to_validate_and_extract}") + else: + self.logger.warning(f" -> Kein Wiki-Artikel für Tochter '{company_name}' via Standard-Suche gefunden.") + url_to_validate_and_extract = "Kein Artikel gefunden" # Signalwert + final_wiki_data['url'] = url_to_validate_and_extract # Dies wird direkt in R geschrieben + 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 + + # Fallback, wenn immer noch keine URL zum Extrahieren, aber R valide war (z.B. bei _needs_wiki_processing=True wegen Z leer, aber R gut) + if url_to_validate_and_extract is None and r_url_is_valid_looking : + url_to_validate_and_extract = current_wiki_url_in_sheet_for_daughter + name_for_wiki_article_validation = company_name + source_of_wiki_data_origin = "Tochter (aus R, Fallback)" + self.logger.info(f" Zeile {row_num_in_sheet}: Fallback, nutze URL aus R: {url_to_validate_and_extract} für Validierung/Extraktion.") + # --- ENDE: STANDARD-LOGIK FÜR TOCHTER-WIKI-SUCHE --- + # --- START: VALIDIERUNG UND DATENEXTRAKTION DER ERMITTELTEN URL --- + artikel_ist_valide_fuer_extraktion = False # Zurücksetzen für diesen Abschnitt + if url_to_validate_and_extract and isinstance(url_to_validate_and_extract, str) and \ + url_to_validate_and_extract.lower() not in ["k.a.", "kein artikel gefunden"] and \ + not url_to_validate_and_extract.startswith("FEHLER"): + + self.logger.info(f" Zeile {row_num_in_sheet}: Artikel '{url_to_validate_and_extract}' wird nun gegen '{name_for_wiki_article_validation}' validiert (Herkunft: {source_of_wiki_data_origin})...") + page_obj_for_validation = None # Explizit initialisieren + try: + if url_to_validate_and_extract.startswith("http"): + page_obj_for_validation = wikipedia.page(url=url_to_validate_and_extract, auto_suggest=False, preload=False) + else: # Annahme: Es ist ein Titel + page_obj_for_validation = wikipedia.page(title=url_to_validate_and_extract, auto_suggest=False, preload=False) + + # Die Validierung verwendet die angepasste _validate_article Methode des WikiScrapers + if self.wiki_scraper._validate_article(page_obj_for_validation, name_for_wiki_article_validation, website_url): # website_url ist immer die der Tochter + artikel_ist_valide_fuer_extraktion = True + url_to_validate_and_extract = page_obj_for_validation.url # Kanonische URL übernehmen + self.logger.info(f" -> Artikel '{page_obj_for_validation.title}' (URL: {url_to_validate_and_extract}) erfolgreich gegen '{name_for_wiki_article_validation}' validiert.") + else: # Artikel nicht valide + self.logger.warning(f" -> Artikel '{page_obj_for_validation.title}' NICHT gegen '{name_for_wiki_article_validation}' validiert.") + additional_info_for_af_col += f"WARNUNG: Artikel '{page_obj_for_validation.url}' nicht valide für '{name_for_wiki_article_validation}'. " + final_wiki_data['url'] = page_obj_for_validation.url # Trotzdem URL speichern + for key_to_clear in ['sitz_stadt', 'sitz_land', 'first_paragraph', 'branche', 'umsatz', 'mitarbeiter', 'categories']: + final_wiki_data[key_to_clear] = 'k.A. (Artikel nicht valide)' if key_to_clear == 'first_paragraph' else 'k.A.' + wiki_data_updated_in_this_run = True # Es gab einen Versuch, die URL zu ändern/setzen - if reset_ac_aa and ac_current_value != "INFO_PARENT_WIKI": # Nicht zurücksetzen, wenn es gerade erst auf Parent-Info gesetzt wurde - self.logger.info(f" Zeile {row_num_in_sheet}: Setze AC ('Chat Wiki Konsistenzpruefung') auf '?' und leere AA ('Wiki Verif. Timestamp') aufgrund neuer/re-evaluierter Wiki-Daten.") - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Chat Wiki Konsistenzpruefung"] + 1)}{row_num_in_sheet}', 'values': [['?']]}) - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Wiki Verif. Timestamp"] + 1)}{row_num_in_sheet}', 'values': [['']]}) - # Auch die abhängigen Spalten AD, AE leeren - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Chat Begründung Wiki Inkonsistenz"] + 1)}{row_num_in_sheet}', 'values': [['']]}) - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Chat Vorschlag Wiki Artikel"] + 1)}{row_num_in_sheet}', 'values': [['']]}) + except wikipedia.exceptions.PageError: + self.logger.warning(f" -> Wiki-Seite für '{url_to_validate_and_extract}' nicht gefunden (PageError) bei Validierungsversuch.") + additional_info_for_af_col += f"FEHLER: Seite '{url_to_validate_and_extract}' nicht gefunden. " + final_wiki_data['url'] = f"Fehler: Seite '{url_to_validate_and_extract}' nicht gefunden" + wiki_data_updated_in_this_run = True + except wikipedia.exceptions.DisambiguationError as e_dis_val_wiki: + self.logger.warning(f" -> Wiki-Seite für '{url_to_validate_and_extract}' ist eine Begriffsklärung: {e_dis_val_wiki.title}. Optionen: {str(e_dis_val_wiki.options)[:100]}...") + additional_info_for_af_col += f"FEHLER: Seite '{url_to_validate_and_extract}' ist Begriffsklärung. " + final_wiki_data['url'] = f"Fehler: Begriffsklärung ({url_to_validate_and_extract})" + wiki_data_updated_in_this_run = True + except Exception as e_val_load_page_wiki: + self.logger.error(f" -> Unerwarteter Fehler beim Laden/Validieren der Wiki-Seite '{url_to_validate_and_extract}': {e_val_load_page_wiki}") + additional_info_for_af_col += f"FEHLER: Laden/Validieren von '{url_to_validate_and_extract}' fehlgeschlagen. " + final_wiki_data['url'] = f"Fehler Validierung: {str(e_val_load_page_wiki)[:50]}" + wiki_data_updated_in_this_run = True + + if artikel_ist_valide_fuer_extraktion: + self.logger.debug(f" -> Extrahiere Wiki-Daten von validierter URL ({source_of_wiki_data_origin}): {url_to_validate_and_extract[:100]}...") + try: + extracted_data = self.wiki_scraper.extract_company_data(url_to_validate_and_extract) + if extracted_data and isinstance(extracted_data, dict) and extracted_data.get('url') != 'k.A.': + final_wiki_data = extracted_data # Überschreibe die Arbeitskopie + wiki_data_updated_in_this_run = True + self.logger.info(f" -> Datenextraktion von {url_to_validate_and_extract[:100]}... ({source_of_wiki_data_origin}) erfolgreich.") + + if source_of_wiki_data_origin.startswith("Parent"): + ac_wert_fuer_parent_wiki = "INFO_PARENT_WIKI" # Spezieller Status + self.logger.info(f" -> Setze AC auf '{ac_wert_fuer_parent_wiki}', da Wiki-Daten von Parent.") + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Chat Wiki Konsistenzpruefung"] + 1)}{row_num_in_sheet}', 'values': [[ac_wert_fuer_parent_wiki]]}) + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Chat Begründung Wiki Inkonsistenz"] + 1)}{row_num_in_sheet}', 'values': [[f"Daten von Parent: {name_for_wiki_article_validation}"]]}) + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Chat Vorschlag Wiki Artikel"] + 1)}{row_num_in_sheet}', 'values': [['']]}) + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Wiki Verif. Timestamp"] + 1)}{row_num_in_sheet}', 'values': [['']]}) + additional_info_for_af_col = f"INFO: Wiki-Daten von Parent '{name_for_wiki_article_validation}' übernommen. " # Überschreibe, da erfolgreich + else: # Fehler bei Extraktion oder leeres Ergebnis + self.logger.error(f" -> Fehler bei Datenextraktion von {url_to_validate_and_extract[:100]}... (Extraktion leer/ungültig).") + final_wiki_data['url'] = url_to_validate_and_extract + for key_to_clear in ['sitz_stadt', 'sitz_land', 'first_paragraph', 'branche', 'umsatz', 'mitarbeiter', 'categories']: + final_wiki_data[key_to_clear] = 'k.A. (Extraktion fehlgeschlagen)' if key_to_clear == 'first_paragraph' else 'k.A.' + wiki_data_updated_in_this_run = True + except Exception as e_wiki_extract_final_val: + self.logger.error(f"FEHLER bei Wikipedia Datenextraktion von {url_to_validate_and_extract[:100]}...: {e_wiki_extract_final_val}") + final_wiki_data['url'] = url_to_validate_and_extract + for key_to_clear in ['sitz_stadt', 'sitz_land', 'first_paragraph', 'branche', 'umsatz', 'mitarbeiter', 'categories']: + final_wiki_data[key_to_clear] = f'k.A. (FEHLER Extraktion)' if key_to_clear == 'first_paragraph' else 'k.A.' + wiki_data_updated_in_this_run = True + + # --- Sheet Updates für Wiki-Daten (R-Y) und Timestamps (Z, AA, AB) --- + if wiki_data_updated_in_this_run: # Nur updaten, wenn sich was geändert hat oder Fehler auftrat + 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.')]]}) + + # Timestamp für Wikipedia-Extraktion (Z) immer setzen, wenn der Wiki-Schritt lief + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Wikipedia Timestamp"] + 1)}{row_num_in_sheet}', 'values': [[now_timestamp]]}) + # Spalte AF Begründung + current_af_val = self._get_cell_value_safe(row_data, "Begründung bei Abweichung").strip() + if additional_info_for_af_col: + new_af_val = (current_af_val + "; " + additional_info_for_af_col if current_af_val else additional_info_for_af_col).strip() + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Begründung bei Abweichung"] + 1)}{row_num_in_sheet}', 'values': [[new_af_val]]}) + + # Reset AC, AA, AD, AE falls nötig (Logik von oben wiederholt, angepasst) + ac_current_value = self._get_cell_value_safe(row_data, "Chat Wiki Konsistenzpruefung").strip() + reset_ac_aa_final = False + if final_wiki_data.get('url', 'k.A.').lower() not in ["k.a.", "kein artikel gefunden"] and not final_wiki_data.get('url', '').startswith("FEHLER"): + if source_of_wiki_data_origin.startswith("Parent") and ac_current_value != "INFO_PARENT_WIKI": + # Für Parent-Daten wurde AC schon oben auf INFO_PARENT_WIKI gesetzt, hier nicht nochmal ändern + pass + elif current_wiki_url_in_sheet_for_daughter != final_wiki_data.get('url'): + reset_ac_aa_final = True + elif force_reeval: + reset_ac_aa_final = True + elif self._get_cell_value_safe(row_data, "Chat Wiki Konsistenzpruefung").strip().upper() == "X (URL COPIED)": # status_ac_is_reparse + reset_ac_aa_final = True + + if reset_ac_aa_final and ac_current_value != "INFO_PARENT_WIKI": + self.logger.info(f" Zeile {row_num_in_sheet}: Setze AC auf '?' und leere AA, AD, AE aufgrund neuer/re-evaluierter Wiki-Daten für '{name_for_wiki_article_validation}'.") + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Chat Wiki Konsistenzpruefung"] + 1)}{row_num_in_sheet}', 'values': [['?']]}) # AC + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Wiki Verif. Timestamp"] + 1)}{row_num_in_sheet}', 'values': [['']]}) # AA + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Chat Begründung Wiki Inkonsistenz"] + 1)}{row_num_in_sheet}', 'values': [['']]}) # AD + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Chat Vorschlag Wiki Artikel"] + 1)}{row_num_in_sheet}', 'values': [['']]}) # AE + + # --- 2b. System Vorschlag Parent Account (Spalte O, P, Q) --- + # Dieser Schritt wird ausgeführt, wenn 'wiki' in steps_to_run enthalten ist (oder ein spezifischer 'parent_suggest'-Schritt) + run_parent_suggest_step = 'wiki' in steps_to_run # Oder ein dedizierter Schritt-Key - # 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).") + system_vorschlag_parent_o_value = self._get_cell_value_safe(row_data, "System Vorschlag Parent Account").strip() + parent_vorschlag_status_p_value = self._get_cell_value_safe(row_data, "Parent Vorschlag Status").strip() - # --- Ende Wikipedia Handling --- + needs_parent_suggestion = (not system_vorschlag_parent_o_value or system_vorschlag_parent_o_value.lower() == 'k.a.') and parent_vorschlag_status_p_value != '-' + if force_reeval and parent_vorschlag_status_p_value != '-': + needs_parent_suggestion = True # Auch wenn O schon gefüllt war, aber nicht abgelehnt + + if run_parent_suggest_step and needs_parent_suggestion: + any_processing_done = True # Zählt als Verarbeitung + self.logger.info(f" Zeile {row_num_in_sheet}: Versuche Parent-Account vorzuschlagen...") + try: + # _suggest_parent_account braucht jetzt auch CRM-Daten und die aktuellen Wiki-Daten als Input + suggested_parent_info = self._suggest_parent_account( + company_name=company_name, + website_url=website_url, + crm_beschreibung=crm_beschreibung, + # final_wiki_data enthält hier die möglicherweise vom Parent (O) stammenden Wiki-Daten, + # was für die Heuristik relevant sein kann (z.B. um nicht sich selbst vorzuschlagen). + # Es ist wichtig, dass _suggest_parent_account dies berücksichtigt. + wiki_data_dict=final_wiki_data + ) + + if suggested_parent_info and suggested_parent_info.get("parent_name"): + # Nur schreiben, wenn der Vorschlag sich vom aktuellen Wert in D unterscheidet (falls D gesetzt) + # oder D leer ist. Und wenn der Vorschlag nicht der Firmenname selbst ist. + if suggested_parent_info["parent_name"].strip().lower() != company_name.strip().lower() and \ + (not parent_account_name_d or suggested_parent_info["parent_name"].strip().lower() != parent_account_name_d.strip().lower()): + + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["System Vorschlag Parent Account"] + 1)}{row_num_in_sheet}', 'values': [[suggested_parent_info["parent_name"]]]}) # Spalte O + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Parent Vorschlag Status"] + 1)}{row_num_in_sheet}', 'values': [['?']]}) # Spalte P, initial '?' + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Parent Vorschlag Timestamp"] + 1)}{row_num_in_sheet}', 'values': [[now_timestamp]]}) # Spalte Q + self.logger.info(f" -> Parent-Vorschlag für O: '{suggested_parent_info['parent_name']}' (Grund: {suggested_parent_info.get('reason', 'N/A')})") + else: + self.logger.info(f" -> Parent-Vorschlag '{suggested_parent_info['parent_name']}' ist identisch mit Firma oder D. Kein Update für O.") + # Optional: Timestamp trotzdem setzen, dass versucht wurde? + if system_vorschlag_parent_o_value.lower() == 'k.a.' or not system_vorschlag_parent_o_value: # Wenn O vorher leer war + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Parent Vorschlag Timestamp"] + 1)}{row_num_in_sheet}', 'values': [[now_timestamp]]}) - # else if run_wiki_step: - # Der Wiki Schritt war angefordert, aber nicht noetig basierend auf Status/Re-Eval. - # Die lokalen Variablen final_wiki_data behaelt die initialen Werte (current_...). - # self.logger.debug(f"Zeile {row_num_in_sheet}: Ueberspringe WIKI Suche/Extraktion (AN vorhanden, S nicht 'X (URL Copied)' und kein Re-Eval).") # Zu viel Laerm im Debug + elif suggested_parent_info and suggested_parent_info.get("status") == "NO_SUGGESTION": + self.logger.info(f" -> Kein Parent-Vorschlag gemacht (Grund: {suggested_parent_info.get('reason', 'N/A')}). P/Q nicht geändert, falls O leer war.") + # Wenn O vorher leer war, Timestamp setzen, dass es versucht wurde. + if system_vorschlag_parent_o_value.lower() == 'k.a.' or not system_vorschlag_parent_o_value: + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Parent Vorschlag Timestamp"] + 1)}{row_num_in_sheet}', 'values': [[now_timestamp]]}) - - # --- Der Code fuer den naechsten Verarbeitungsschritt (ChatGPT Evaluationen) folgt im naechsten Block --- - # Definition der Methode _process_single_row wird in der naechsten Nachricht fortgesetzt. + else: + self.logger.info(f" -> Kein Parent-Vorschlag von _suggest_parent_account erhalten oder ungültiges Format.") + except Exception as e_suggest_parent: + self.logger.error(f" -> Fehler bei _suggest_parent_account für Zeile {row_num_in_sheet}: {e_suggest_parent}") + # --- ENDE: 2b. Parent-Account-Vorschlag --- + + # --- Ende des Wikipedia-Blocks --- + # Der nächste Block wäre ChatGPT-Evaluationen + # ====================================================================== # --- 3. ChatGPT Evaluationen (Branch, FSM, Emp, Umsatz Schaetzungen etc.) --- - # Dieser Schritt wird ausgefuehrt, wenn 'chat' in steps_to_run enthalten ist UND - # (_needs_chat_evaluations True ist ODER force_reeval True ist). - # _needs_chat_evaluations prueft BC (alt AO) oder ob Wiki-Daten in diesem Lauf gerade aktualisiert wurden. - # Nutzt interne Helfer: _get_cell_value_safe, _needs_chat_evaluations. - # Nutzt globale Helfer: COLUMN_MAP, logger, datetime, time, - # evaluate_branche_chatgpt (Block 10), - # get_numeric_filter_value (Block 5). - # Nutzt lokale Variablen: crm_branche, crm_beschreibung, final_wiki_data, website_summary, wiki_data_updated_in_this_run. - run_chat_step = 'chat' in steps_to_run + # wiki_data_updated_in_this_run wurde im Wikipedia-Block oben gesetzt chat_processing_needed_based_on_status = self._needs_chat_evaluations(row_data, force_reeval, wiki_data_updated_in_this_run) if run_chat_step and chat_processing_needed_based_on_status: any_processing_done = True - chat_eval_just_ran = True + chat_eval_just_ran = True # Wichtig für ML-Schritt später - grund_message_parts = [] - if force_reeval: grund_message_parts.append('Re-Eval') - if not self._get_cell_value_safe(row_data, "Timestamp letzte Pruefung").strip(): grund_message_parts.append('BC (Timestamp letzte Pruefung) leer') # Neuer Schlüssel BC - if wiki_data_updated_in_this_run: grund_message_parts.append('Wiki Daten gerade aktualisiert') - - grund_message = ", ".join(filter(None, grund_message_parts)) or "Unbekannter Grund" + # ... (Grund Message für Log erstellen) ... + grund_message_parts_chat = [] + if force_reeval: grund_message_parts_chat.append('Re-Eval') + if not self._get_cell_value_safe(row_data, "Timestamp letzte Prüfung").strip(): grund_message_parts_chat.append('BN (Timestamp letzte Prüfung) leer') + if wiki_data_updated_in_this_run: grund_message_parts_chat.append('Wiki Daten gerade aktualisiert') + grund_message_chat = ", ".join(filter(None, grund_message_parts_chat)) or "Unbekannter Grund (Chat)" + self.logger.info(f"Zeile {row_num_in_sheet}: Fuehre CHATGPT Evaluationen & Plausi aus (Grund: {grund_message_chat})...") - self.logger.info(f"Zeile {row_num_in_sheet}: Fuehre CHATGPT Evaluationen aus (Grund: {grund_message})...") - - # --- 3a. Branchen-Einstufung (AH, AI, AJ, AK) --- - self.logger.info(f"Zeile {row_num_in_sheet}: Starte Branchen-Einstufung ueber ChatGPT...") # Log-Level auf INFO geändert + # --- 3a. Branchen-Einstufung (AL, AM, AN, AO) --- + # ... (Dieser Teil bleibt wie er war, nutzt final_wiki_data, website_summary) ... + # ... (Logik für evaluate_branche_chatgpt und Updates für AL, AM, AN, AO) ... + self.logger.info(f"Zeile {row_num_in_sheet}: Starte Branchen-Einstufung ueber ChatGPT...") try: + # final_wiki_data enthält hier ggf. die Daten des Parents branch_result = evaluate_branche_chatgpt( crm_branche, crm_beschreibung, @@ -4835,164 +4984,94 @@ class DataProcessor: final_wiki_data.get('categories', 'k.A.'), website_summary ) + # ... (Updates für Spalten AL, AM, AN, AO) + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Chat Vorschlag Branche"] + 1)}{row_num_in_sheet}', 'values': [[branch_result.get("branch", "FEHLER BRANCH")]]}) + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Chat Branche Konfidenz"] + 1)}{row_num_in_sheet}', 'values': [[branch_result.get("confidence", "N/A CONF")]]}) + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Chat Konsistenz Branche"] + 1)}{row_num_in_sheet}', 'values': [[branch_result.get("consistency", "error CONS")]]}) + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Chat Begruendung Abweichung Branche"] + 1)}{row_num_in_sheet}', 'values': [[branch_result.get("justification", "No JUST")]]}) + except Exception as e_branch_eval_row: + self.logger.error(f"FEHLER bei Branchen-Einstufung für Zeile {row_num_in_sheet}: {e_branch_eval_row}") + # ... (Fehlerwerte schreiben) ... + + # --- 3b, 3c, 3d: Weitere ChatGPT Evaluationen (FSM, Mitarbeiter, Umsatz) --- + # ... (Diese bleiben strukturell gleich, nutzen aber das ggf. aktualisierte final_wiki_data) ... + # Zum Beispiel für Mitarbeiterschätzung: + # if not final_wiki_data.get('mitarbeiter') or final_wiki_data.get('mitarbeiter', 'k.A.').lower() == 'k.a.': + # # ... call_openai_chat für Mitarbeiterschätzung ... + # ... etc. für andere ChatGPT-Schritte ... + + # HIER BEGINNT DIE WICHTIGE ÄNDERUNG FÜR KONSOLIDIERUNG UND PLAUSI + # --- 3e. Konsolidierung Umsatz/Mitarbeiter (BD, BE) --- + self.logger.debug(f" Zeile {row_num_in_sheet}: Konsolidiere Umsatz (BD) und Mitarbeiter (BE) unter Berücksichtigung von Parent (D)...") + final_umsatz_str_konsolidiert = "k.A." + final_ma_str_konsolidiert = "k.A." + try: + crm_umsatz_val_str = self._get_cell_value_safe(row_data, "CRM Umsatz") # Spalte L + wiki_umsatz_val_str = final_wiki_data.get('umsatz', 'k.A.') # Aus final_wiki_data (kann Parent sein), Spalte W + crm_ma_val_str = self._get_cell_value_safe(row_data, "CRM Anzahl Mitarbeiter") # Spalte M + wiki_ma_val_str = final_wiki_data.get('mitarbeiter', 'k.A.') # Aus final_wiki_data, Spalte X - self.logger.debug(f"Zeile {row_num_in_sheet}: branch_result von evaluate_branche_chatgpt: {branch_result}") # DEBUG 1 + # Numerische Werte für die Logik holen + num_crm_umsatz = get_numeric_filter_value(crm_umsatz_val_str, is_umsatz=True) + num_wiki_umsatz = get_numeric_filter_value(wiki_umsatz_val_str, is_umsatz=True) + num_crm_ma = get_numeric_filter_value(crm_ma_val_str, is_umsatz=False) + num_wiki_ma = get_numeric_filter_value(wiki_ma_val_str, is_umsatz=False) - # Update für "Chat Vorschlag Branche" - vorschlag_key = "Chat Vorschlag Branche" - vorschlag_col_idx = COLUMN_MAP.get(vorschlag_key) - if vorschlag_col_idx is not None: - range_str_vorschlag = f'{self.sheet_handler._get_col_letter(vorschlag_col_idx + 1)}{row_num_in_sheet}' - wert_vorschlag = branch_result.get("branch", "FEHLER BRANCH") - updates.append({'range': range_str_vorschlag, 'values': [[wert_vorschlag]]}) - self.logger.debug(f"Zeile {row_num_in_sheet}: Update vorbereitet für {vorschlag_key} ({range_str_vorschlag}): '{wert_vorschlag}'") + self.logger.debug(f" Konsolidierung Input: CRM_U(L)='{num_crm_umsatz}', Wiki_U(W)='{num_wiki_umsatz}' (aus {source_of_wiki_data_origin}), CRM_M(M)='{num_crm_ma}', Wiki_M(X)='{num_wiki_ma}' (aus {source_of_wiki_data_origin}), Parent_D='{parent_account_name_d}'") + + if parent_account_name_d and parent_account_name_d.lower() != 'k.a.': + # Parent-Account (D) ist GEFÜLLT: Primär CRM-Daten der Tochter verwenden. + self.logger.info(f" -> Parent D ('{parent_account_name_d}') ist gesetzt. Konsolidiere primär mit CRM-Daten der Tochter für BD/BE.") + final_num_umsatz = num_crm_umsatz if num_crm_umsatz > 0 else num_wiki_umsatz # Wiki als Fallback, falls CRM der Tochter fehlt/0 ist + final_num_ma = num_crm_ma if num_crm_ma > 0 else num_wiki_ma # Wiki als Fallback else: - self.logger.error(f"Zeile {row_num_in_sheet}: Schlüssel '{vorschlag_key}' nicht in COLUMN_MAP gefunden!") - - # NEU: Konfidenz-Score speichern - konfidenz_key = "Chat Branche Konfidenz" - konfidenz_col_idx = COLUMN_MAP.get(konfidenz_key) - if konfidenz_col_idx is not None: - range_str_konfidenz = f'{self.sheet_handler._get_col_letter(konfidenz_col_idx + 1)}{row_num_in_sheet}' - wert_konfidenz = branch_result.get("confidence", "N/A CONF") - updates.append({'range': range_str_konfidenz, 'values': [[wert_konfidenz]]}) - self.logger.debug(f"Zeile {row_num_in_sheet}: Update vorbereitet für {konfidenz_key} ({range_str_konfidenz}): '{wert_konfidenz}'") - else: - self.logger.error(f"Zeile {row_num_in_sheet}: Schlüssel '{konfidenz_key}' nicht in COLUMN_MAP gefunden!") - - # Update für "Chat Konsistenz Branche" - konsistenz_key = "Chat Konsistenz Branche" - konsistenz_col_idx = COLUMN_MAP.get(konsistenz_key) - if konsistenz_col_idx is not None: - range_str_konsistenz = f'{self.sheet_handler._get_col_letter(konsistenz_col_idx + 1)}{row_num_in_sheet}' - wert_konsistenz = branch_result.get("consistency", "error CONS") - updates.append({'range': range_str_konsistenz, 'values': [[wert_konsistenz]]}) - self.logger.debug(f"Zeile {row_num_in_sheet}: Update vorbereitet für {konsistenz_key} ({range_str_konsistenz}): '{wert_konsistenz}'") - else: - self.logger.error(f"Zeile {row_num_in_sheet}: Schlüssel '{konsistenz_key}' nicht in COLUMN_MAP gefunden!") - - # Update für "Chat Begruendung Abweichung Branche" - begruendung_key = "Chat Begruendung Abweichung Branche" - begruendung_col_idx = COLUMN_MAP.get(begruendung_key) - if begruendung_col_idx is not None: - range_str_begruendung = f'{self.sheet_handler._get_col_letter(begruendung_col_idx + 1)}{row_num_in_sheet}' - wert_begruendung = branch_result.get("justification", "Keine Begruendung JUST") - updates.append({'range': range_str_begruendung, 'values': [[wert_begruendung]]}) - self.logger.debug(f"Zeile {row_num_in_sheet}: Update vorbereitet für {begruendung_key} ({range_str_begruendung}): '{str(wert_begruendung)[:50]}...'") - else: - self.logger.error(f"Zeile {row_num_in_sheet}: Schlüssel '{begruendung_key}' nicht in COLUMN_MAP gefunden!") - - except Exception as e_branch_eval: - self.logger.error(f"FEHLER bei Branchen-Einstufung ueber ChatGPT fuer Zeile {row_num_in_sheet}: {e_branch_eval}") - self.logger.debug(traceback.format_exc()) - error_msg = f"Fehler Eval: {str(e_branch_eval)[:100]}..." - # Fehlerwerte für alle relevanten Spalten schreiben - if COLUMN_MAP.get("Chat Vorschlag Branche") is not None: - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Chat Vorschlag Branche"] + 1)}{row_num_in_sheet}', 'values': [['FEHLER']]}) - if COLUMN_MAP.get("Chat Branche Konfidenz") is not None: - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Chat Branche Konfidenz"] + 1)}{row_num_in_sheet}', 'values': [['N/A (Eval Fehler)']]}) - if COLUMN_MAP.get("Chat Konsistenz Branche") is not None: - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Chat Konsistenz Branche"] + 1)}{row_num_in_sheet}', 'values': [['error']]}) - if COLUMN_MAP.get("Chat Begruendung Abweichung Branche") is not None: - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Chat Begruendung Abweichung Branche"] + 1)}{row_num_in_sheet}', 'values': [[error_msg]]}) - pass - - # --- 3e. Konsolidierung Umsatz/Mitarbeiter (AY, AZ) --- - self.logger.debug(" -> Konsolidiere Umsatz (AY) und Mitarbeiter (AZ) (Wiki > CRM Logik)...") - final_umsatz_str_konsolidiert = "k.A." # Default - final_ma_str_konsolidiert = "k.A." # Default - try: - crm_umsatz_val = self._get_cell_value_safe(row_data, "CRM Umsatz") - wiki_umsatz_val = final_wiki_data.get('umsatz', 'k.A.') - crm_ma_val = self._get_cell_value_safe(row_data, "CRM Anzahl Mitarbeiter") - wiki_ma_val = final_wiki_data.get('mitarbeiter', 'k.A.') - - # get_numeric_filter_value gibt 0.0 oder 0 zurück, wenn der Input nicht positiv numerisch ist oder leer/k.A. - num_crm_umsatz = get_numeric_filter_value(crm_umsatz_val, is_umsatz=True) - num_wiki_umsatz = get_numeric_filter_value(wiki_umsatz_val, is_umsatz=True) - num_crm_ma = get_numeric_filter_value(crm_ma_val, is_umsatz=False) - num_wiki_ma = get_numeric_filter_value(wiki_ma_val, is_umsatz=False) - - # Logge die numerischen Werte VOR der Konsolidierung - self.logger.debug(f" Konsolidierung Input: num_crm_umsatz={num_crm_umsatz}, num_wiki_umsatz={num_wiki_umsatz}, num_crm_ma={num_crm_ma}, num_wiki_ma={num_wiki_ma}") - - # Konsolidierung: Wiki > CRM. Wenn Wiki keinen positiven Wert hat, nimm CRM. - # Wenn beide keinen positiven Wert haben, bleibt es bei 0. - final_num_umsatz = num_wiki_umsatz if num_wiki_umsatz > 0 else num_crm_umsatz - final_num_ma = num_wiki_ma if num_wiki_ma > 0 else num_crm_ma + # Parent-Account (D) ist LEER: Standardlogik Wiki > CRM. + self.logger.debug(f" -> Parent D leer. Standardkonsolidierung Wiki > CRM für BD/BE.") + final_num_umsatz = num_wiki_umsatz if num_wiki_umsatz > 0 else num_crm_umsatz + final_num_ma = num_wiki_ma if num_wiki_ma > 0 else num_crm_ma + + final_umsatz_str_konsolidiert = str(int(round(final_num_umsatz))) if final_num_umsatz > 0 else 'k.A.' + final_ma_str_konsolidiert = str(int(round(final_num_ma))) if final_num_ma > 0 else 'k.A.' - # String-Konvertierung: "0" bedeutet "unbekannt" -> "k.A." - # Nur echt positive Werte werden als Zahl geschrieben. - final_umsatz_str_konsolidiert = str(int(round(final_num_umsatz))) if final_num_umsatz > 0 else 'k.A.' - final_ma_str_konsolidiert = str(int(round(final_num_ma))) if final_num_ma > 0 else 'k.A.' - - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Finaler Umsatz (Wiki>CRM)"] + 1)}{row_num_in_sheet}', 'values': [[final_umsatz_str_konsolidiert]]}) - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Finaler Mitarbeiter (Wiki>CRM)"] + 1)}{row_num_in_sheet}', 'values': [[final_ma_str_konsolidiert]]}) - # Das Log hier sollte jetzt die String-Werte ausgeben, die auch geschrieben werden: - self.logger.debug(f" -> Konsolidiert (Sheet-Werte): Umsatz='{final_umsatz_str_konsolidiert}', MA='{final_ma_str_konsolidiert}'") - except Exception as e_consolidate: - self.logger.error(f"FEHLER bei Konsolidierung Umsatz/Mitarbeiter fuer Zeile {row_num_in_sheet}: {e_consolidate}") - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Finaler Umsatz (Wiki>CRM)"] + 1)}{row_num_in_sheet}', 'values': [['FEHLER KONSO']]}) - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Finaler Mitarbeiter (Wiki>CRM)"] + 1)}{row_num_in_sheet}', 'values': [['FEHLER KONSO']]}) - final_umsatz_str_konsolidiert = "FEHLER KONSO" - final_ma_str_konsolidiert = "FEHLER KONSO" + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Finaler Umsatz (Wiki>CRM)"] + 1)}{row_num_in_sheet}', 'values': [[final_umsatz_str_konsolidiert]]}) + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Finaler Mitarbeiter (Wiki>CRM)"] + 1)}{row_num_in_sheet}', 'values': [[final_ma_str_konsolidiert]]}) + self.logger.debug(f" -> Konsolidiert für BD/BE (Sheet-Werte): Umsatz='{final_umsatz_str_konsolidiert}', MA='{final_ma_str_konsolidiert}'") + except Exception as e_consolidate_row_final: + self.logger.error(f"FEHLER bei Konsolidierung Umsatz/Mitarbeiter (BD/BE) für Zeile {row_num_in_sheet}: {e_consolidate_row_final}") + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Finaler Umsatz (Wiki>CRM)"] + 1)}{row_num_in_sheet}', 'values': [['FEHLER_KONSO']]}) + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Finaler Mitarbeiter (Wiki>CRM)"] + 1)}{row_num_in_sheet}', 'values': [['FEHLER_KONSO']]}) + final_umsatz_str_konsolidiert = "FEHLER_KONSO" # Für Plausi-Input + final_ma_str_konsolidiert = "FEHLER_KONSO" - # --- NEU: 3f. Plausibilitäts-Checks durchführen (BG-BM) --- - self.logger.debug(f" Zeile {row_num_in_sheet}: Führe Plausibilitäts-Checks durch...") - plausi_check_erfolgreich_durchgefuehrt = False # Flag + # --- 3f. Plausibilitäts-Checks durchführen (BG-BM) --- + # Der Aufruf von _check_financial_plausibility muss jetzt parent_account_name_d (Spalte D) übergeben bekommen. + self.logger.debug(f" Zeile {row_num_in_sheet}: Führe Plausibilitäts-Checks durch (Parent D: '{parent_account_name_d}')...") try: - # ... (Erstellung von plausi_input_data wie zuvor) ... plausi_input_data = { - "Finaler Umsatz (Wiki>CRM)": final_umsatz_str_konsolidiert, - "Finaler Mitarbeiter (Wiki>CRM)": final_ma_str_konsolidiert, - "CRM Umsatz": self._get_cell_value_safe(row_data, "CRM Umsatz"), - "Wiki Umsatz": self._get_cell_value_safe(row_data, "Wiki Umsatz"), - "CRM Anzahl Mitarbeiter": self._get_cell_value_safe(row_data, "CRM Anzahl Mitarbeiter"), - "Wiki Mitarbeiter": self._get_cell_value_safe(row_data, "Wiki Mitarbeiter") + "Finaler Umsatz (Wiki>CRM)": final_umsatz_str_konsolidiert, # Aus Schritt 3e + "Finaler Mitarbeiter (Wiki>CRM)": final_ma_str_konsolidiert, # Aus Schritt 3e + "CRM Umsatz": self._get_cell_value_safe(row_data, "CRM Umsatz"), # Spalte L + "Wiki Umsatz": final_wiki_data.get('umsatz', 'k.A.'), # Spalte W (kann von Parent sein) + "CRM Anzahl Mitarbeiter": self._get_cell_value_safe(row_data, "CRM Anzahl Mitarbeiter"), # Spalte M + "Wiki Mitarbeiter": final_wiki_data.get('mitarbeiter', 'k.A.'), # Spalte X (kann von Parent sein) + "Parent Account Name": parent_account_name_d # WICHTIG: Spalte D übergeben } - plausi_results = self._check_financial_plausibility(plausi_input_data) + plausi_results = self._check_financial_plausibility(plausi_input_data) # Diese Methode muss D berücksichtigen updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Plausibilität Umsatz"] + 1)}{row_num_in_sheet}', 'values': [[plausi_results.get("plaus_umsatz_flag", "ERR_FLAG")]]}) updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Plausibilität Mitarbeiter"] + 1)}{row_num_in_sheet}', 'values': [[plausi_results.get("plaus_ma_flag", "ERR_FLAG")]]}) updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Plausibilität Umsatz/MA Ratio"] + 1)}{row_num_in_sheet}', 'values': [[plausi_results.get("plaus_ratio_flag", "ERR_FLAG")]]}) - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Abweichung Umsatz CRM/Wiki"] + 1)}{row_num_in_sheet}', 'values': [[plausi_results.get("abweichung_umsatz_flag", "ERR_FLAG")]]}) - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Abweichung MA CRM/Wiki"] + 1)}{row_num_in_sheet}', 'values': [[plausi_results.get("abweichung_ma_flag", "ERR_FLAG")]]}) + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Abweichung Umsatz CRM/Wiki"] + 1)}{row_num_in_sheet}', 'values': [[plausi_results.get("abweichung_umsatz_flag", "ERR_FLAG")]]}) # BJ + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Abweichung MA CRM/Wiki"] + 1)}{row_num_in_sheet}', 'values': [[plausi_results.get("abweichung_ma_flag", "ERR_FLAG")]]}) # BK updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Plausibilität Begründung"] + 1)}{row_num_in_sheet}', 'values': [[plausi_results.get("plausi_begruendung_final", "Fehler Begr.")]]}) - plausi_check_erfolgreich_durchgefuehrt = True # Markieren, dass der Check zumindest versucht wurde + except Exception as e_plausi_in_single_row_chat: + self.logger.error(f"FEHLER bei Plausibilitäts-Checks in _process_single_row (Chat-Block) für Zeile {row_num_in_sheet}: {e_plausi_in_single_row_chat}") + # ... (Fehler-Flags für Plausi-Spalten setzen) ... - except Exception as e_plausi_in_single_row: - self.logger.error(f"FEHLER bei Plausibilitäts-Checks in _process_single_row für Zeile {row_num_in_sheet}: {e_plausi_in_single_row}") - # Fehler-Flags für Plausi-Spalten setzen - error_val_plausi = [['FEHLER_PLAUSI_CALL']] - for key_flag_plausi in ["Plausibilität Umsatz", "Plausibilität Mitarbeiter", "Plausibilität Umsatz/MA Ratio", "Abweichung Umsatz CRM/Wiki", "Abweichung MA CRM/Wiki"]: - if COLUMN_MAP.get(key_flag_plausi) is not None: # Sicherstellen, dass der Key existiert - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP[key_flag_plausi] + 1)}{row_num_in_sheet}', 'values': error_val_plausi}) - if COLUMN_MAP.get("Plausibilität Begründung") is not None: - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Plausibilität Begründung"] + 1)}{row_num_in_sheet}', 'values': [[f"Systemfehler Plausi-Call: {str(e_plausi_in_single_row)[:50]}"]]}) - - # Plausi-Timestamp setzen, wenn der Plausi-Block (3f) erreicht wurde - # (entweder erfolgreich oder mit Fehler im try-Block) - if COLUMN_MAP.get("Plausibilität Prüfdatum") is not None: - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Plausibilität Prüfdatum"] + 1)}{row_num_in_sheet}', 'values': [[now_timestamp]]}) - else: - self.logger.error("FEHLER: Schlüssel 'Plausibilität Prüfdatum' nicht in COLUMN_MAP. Kann Timestamp nicht setzen.") - - - # Setze den "Timestamp letzte Prüfung" (BO - für ChatGPT-Evaluationen), da dieser Block lief - if COLUMN_MAP.get("Timestamp letzte Prüfung") is not None: - updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Timestamp letzte Prüfung"] + 1)}{row_num_in_sheet}', 'values': [[now_timestamp]]}) - else: - self.logger.error("FEHLER: Schlüssel 'Timestamp letzte Prüfung' nicht in COLUMN_MAP. Kann Haupt-Timestamp nicht setzen.") - - # else if run_chat_step (aber nicht processing_needed): - # self.logger.debug(f"Zeile {row_num_in_sheet}: Ueberspringe CHATGPT Evaluationen und Plausi-Checks (Timestamp BI gesetzt, Wiki nicht aktualisiert und kein Re-Eval).") - - # else if run_chat_step: - # Die Chat-Schritte waren angefordert, aber nicht noetig basierend auf Status/Re-Eval/Wiki-Update. - # Die lokalen Variablen final_wiki_data und website_summary behalten ihre initialen Werte (current_...). - # chat_eval_just_ran bleibt False. - # self.logger.debug(f"Zeile {row_num_in_sheet}: Ueberspringe CHATGPT Evaluationen (AO vorhanden, Wiki nicht aktualisiert und kein Re-Eval).") # Zu viel Laerm im Debug + # Plausi-Timestamp (BM) und Haupt-Timestamp (BN) setzen + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Plausibilität Prüfdatum"] + 1)}{row_num_in_sheet}', 'values': [[now_timestamp]]}) + updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Timestamp letzte Prüfung"] + 1)}{row_num_in_sheet}', 'values': [[now_timestamp]]}) # --- Der Code fuer den naechsten Verarbeitungsschritt (ML Prediction) folgt im naechsten Block ---