diff --git a/brancheneinstufung.py b/brancheneinstufung.py index 93a7c4b6..759a1669 100644 --- a/brancheneinstufung.py +++ b/brancheneinstufung.py @@ -3209,203 +3209,168 @@ class DataProcessor: # @retry_on_failure # Vorsicht mit Retry auf dieser Ebene für die ganze Zeile def _process_single_row(self, row_num_in_sheet, row_data, process_wiki=True, process_chatgpt=True, process_website=True): """ - Verarbeitet die Daten für eine einzelne Zeile, prüft Timestamps für jeden Teilbereich. - - Args: - row_num_in_sheet (int): Die 1-basierte Zeilennummer im Google Sheet. - row_data (list): Die List der Daten für diese Zeile. - process_wiki (bool): Ob der Wikipedia-Teil ausgeführt werden soll. - process_chatgpt (bool): Ob der ChatGPT-Evaluationsteil ausgeführt werden soll. - process_website (bool): Ob der Website-Teil ausgeführt werden soll. + Verarbeitet die Daten für eine einzelne Zeile, prüft Timestamps für jeden Teilbereich + und priorisiert die URL in Spalte M, wenn Wiki neu verarbeitet wird. """ debug_print(f"--- Starte Verarbeitung für Zeile {row_num_in_sheet} ---") - updates = [] # Sammle alle Updates für diese Zeile + updates = [] now_timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - any_processing_done = False # Flag, ob überhaupt etwas getan wurde + any_processing_done = False - # --- Daten extrahieren (nutze COLUMN_MAP) --- - # Hilfsfunktion, um sicher auf Index zuzugreifen - def get_cell_value(col_key): - idx = COLUMN_MAP.get(col_key) - if idx is not None and len(row_data) > idx: - return row_data[idx] - return "" # Oder None oder einen anderen Standardwert + # Hilfsfunktion für sicheren Zugriff + def get_cell_value(key): + idx = COLUMN_MAP.get(key) + if idx is not None and len(row_data) > idx: return row_data[idx] + return "" company_name = get_cell_value("CRM Name") website_url = get_cell_value("CRM Website") - original_website = website_url # Merken für späteren Vergleich + original_website = website_url crm_branche = get_cell_value("CRM Branche") crm_beschreibung = get_cell_value("CRM Beschreibung") - crm_wiki_url = get_cell_value("CRM Vorschlag Wiki URL") + crm_wiki_url = get_cell_value("CRM Vorschlag Wiki URL") # Wird nur noch als Fallback genutzt + konsistenz_s = get_cell_value("Chat Wiki Konsistenzprüfung") # Für Re-Parse Trigger - # Lese aktuelle Werte für Website Rohtext/Zusammenfassung + # Lese aktuelle Werte, die potenziell überschrieben werden website_raw = get_cell_value("Website Rohtext") or "k.A." website_summary = get_cell_value("Website Zusammenfassung") or "k.A." + wiki_data = { # Lade aktuelle Wiki-Daten für den Fall, dass Wiki nicht neu geparst wird + 'url': get_cell_value("Wiki URL") or 'k.A.', + 'first_paragraph': get_cell_value("Wiki Absatz") or 'k.A.', + 'branche': get_cell_value("Wiki Branche") or 'k.A.', + 'umsatz': get_cell_value("Wiki Umsatz") or 'k.A.', + 'mitarbeiter': get_cell_value("Wiki Mitarbeiter") or 'k.A.', + 'categories': get_cell_value("Wiki Kategorien") or 'k.A.' + } - # --- 1. Website Handling (Lookup, Scrape, Summarize) --- - # Prüfe Timestamp AT (Index 45) + # --- 1. Website Handling (wie zuletzt -> prüft AT) --- website_ts_needed = process_website and not get_cell_value("Website Scrape Timestamp").strip() - if website_ts_needed: debug_print(f"Zeile {row_num_in_sheet}: Starte Website Verarbeitung (Timestamp AT fehlt)...") any_processing_done = True - - # Website Lookup, wenn leer + # --- Lookup --- if not website_url or website_url.strip().lower() == "k.a.": - debug_print(f"Zeile {row_num_in_sheet}: CRM Website fehlt, starte SERP Lookup für '{company_name}'...") - new_website = serp_website_lookup(company_name) - if new_website != "k.A.": - website_url = new_website # Aktualisiere URL für weitere Schritte - debug_print(f"Zeile {row_num_in_sheet}: SERP Lookup erfolgreich: {website_url}") - if website_url != original_website: - updates.append({'range': f'D{row_num_in_sheet}', 'values': [[website_url]]}) - else: - debug_print(f"Zeile {row_num_in_sheet}: SERP Lookup erfolglos.") - - # Website Scraping, wenn URL vorhanden + new_website = serp_website_lookup(company_name) + if new_website != "k.A.": website_url = new_website; debug_print(f"Zeile {row_num_in_sheet}: SERP Lookup: {website_url}"); + if website_url != original_website: updates.append({'range': f'D{row_num_in_sheet}', 'values': [[website_url]]}) + else: debug_print(f"Zeile {row_num_in_sheet}: SERP Lookup erfolglos.") + # --- Scraping --- if website_url and website_url.strip().lower() != "k.a.": - debug_print(f"Zeile {row_num_in_sheet}: Starte Website Scraping für {website_url}...") new_website_raw = get_website_raw(website_url) new_website_summary = summarize_website_content(new_website_raw) - - # Füge Updates nur hinzu, wenn sich etwas geändert hat oder vorher k.A. war - if new_website_raw != website_raw: - updates.append({'range': f'AR{row_num_in_sheet}', 'values': [[new_website_raw]]}) - website_raw = new_website_raw # Aktualisiere lokalen Wert für Chat-Teil - if new_website_summary != website_summary: - updates.append({'range': f'AS{row_num_in_sheet}', 'values': [[new_website_summary]]}) - website_summary = new_website_summary # Aktualisiere lokalen Wert für Chat-Teil + if new_website_raw != website_raw: updates.append({'range': f'AR{row_num_in_sheet}', 'values': [[new_website_raw]]}); website_raw = new_website_raw + if new_website_summary != website_summary: updates.append({'range': f'AS{row_num_in_sheet}', 'values': [[new_website_summary]]}); website_summary = new_website_summary else: - debug_print(f"Zeile {row_num_in_sheet}: Überspringe Website Scraping (keine gültige URL).") - # Setze Rohtext/Summary auf k.A., falls sie vorher was anderes waren? if website_raw != "k.A.": updates.append({'range': f'AR{row_num_in_sheet}', 'values': [['k.A.']]}) if website_summary != "k.A.": updates.append({'range': f'AS{row_num_in_sheet}', 'values': [['k.A.']]}) - website_raw, website_summary = "k.A.", "k.A." # Aktualisiere lokale Werte - - # Setze Website Timestamp (AT) + website_raw, website_summary = "k.A.", "k.A." updates.append({'range': f'AT{row_num_in_sheet}', 'values': [[now_timestamp]]}) - # Version wird am Ende gesetzt - elif process_website: debug_print(f"Zeile {row_num_in_sheet}: Überspringe Website Verarbeitung (Timestamp AT vorhanden).") - # Stelle sicher, dass lokale Variablen website_raw/summary aktuell sind - website_raw = get_cell_value("Website Rohtext") or "k.A." - website_summary = get_cell_value("Website Zusammenfassung") or "k.A." + # --- 2. Wikipedia Handling (ANGEPASSTE LOGIK) --- + # Trigger für Reparsing: AN fehlt ODER Status S ist "X (URL Copied)" + wiki_ts_an_missing = not get_cell_value("Wikipedia Timestamp").strip() + status_s_indicates_reparse = konsistenz_s.strip().upper() == "X (URL COPIED)" + reparse_wiki_needed = process_wiki and (wiki_ts_an_missing or status_s_indicates_reparse) - # --- 2. Wikipedia Handling --- - wiki_data = {} # Wird gefüllt, entweder durch Scraping oder aus Zeile gelesen - wiki_ts_needed = process_wiki and not get_cell_value("Wikipedia Timestamp").strip() - - if wiki_ts_needed: - debug_print(f"Zeile {row_num_in_sheet}: Starte Wikipedia Verarbeitung (Timestamp AN fehlt)...") + if reparse_wiki_needed: + debug_print(f"Zeile {row_num_in_sheet}: Starte Wikipedia Verarbeitung (Grund: AN fehlt? {wiki_ts_an_missing}, S='X (URL Copied)'? {status_s_indicates_reparse})...") any_processing_done = True + new_wiki_data_extracted = None # Wird mit den neuen Daten gefüllt - # Logik für Suche und Extraktion - valid_crm_wiki_url = crm_wiki_url if crm_wiki_url and crm_wiki_url.strip() not in ["", "k.A."] else None - article_page = None - if valid_crm_wiki_url: - debug_print(f"Zeile {row_num_in_sheet}: Prüfe CRM Wiki Vorschlag: {valid_crm_wiki_url}") - page = self.wiki_scraper._fetch_page_content(valid_crm_wiki_url.split('/')[-1]) - # Überprüfe ob website_url hier aktuell ist (könnte durch Lookup geändert sein) - current_website_for_validation = website_url if website_url and website_url != 'k.A.' else original_website - if page and self.wiki_scraper._validate_article(page, company_name, current_website_for_validation): - article_page = page - else: - debug_print(f"Zeile {row_num_in_sheet}: CRM Wiki Vorschlag nicht validiert. Starte Suche...") - article_page = self.wiki_scraper.search_company_article(company_name, current_website_for_validation) + # --- Priorisiere URL aus Spalte M --- + url_to_parse = get_cell_value("Wiki URL").strip() + if url_to_parse and url_to_parse.lower() not in ["k.a.", "kein artikel gefunden"] and url_to_parse.lower().startswith("http"): + debug_print(f" -> Nutze vorhandene URL aus Spalte M: {url_to_parse}") + new_wiki_data_extracted = self.wiki_scraper.extract_company_data(url_to_parse) else: - debug_print(f"Zeile {row_num_in_sheet}: Kein CRM Wiki Vorschlag. Starte Suche...") - current_website_for_validation = website_url if website_url and website_url != 'k.A.' else original_website - article_page = self.wiki_scraper.search_company_article(company_name, current_website_for_validation) + # --- Nur wenn M leer/ungültig ist, starte die Suche --- + debug_print(f" -> Spalte M ('{url_to_parse}') ungültig/leer. Starte Wiki-Suche...") + valid_crm_wiki_url = crm_wiki_url if crm_wiki_url and crm_wiki_url.strip() not in ["", "k.A."] else None + article_page = None + current_website_for_validation = website_url if website_url and website_url != 'k.A.' else original_website # Nutze aktuelle URL + if valid_crm_wiki_url: + debug_print(f" -> Prüfe CRM Vorschlag L: {valid_crm_wiki_url}") + page = self.wiki_scraper._fetch_page_content(valid_crm_wiki_url.split('/')[-1]) + if page and self.wiki_scraper._validate_article(page, company_name, current_website_for_validation): article_page = page + else: debug_print(f" -> CRM Vorschlag L nicht validiert. Starte Suche..."); article_page = self.wiki_scraper.search_company_article(company_name, current_website_for_validation) + else: + debug_print(f" -> Kein CRM Vorschlag L. Starte Suche...") + article_page = self.wiki_scraper.search_company_article(company_name, current_website_for_validation) - if article_page: - debug_print(f"Zeile {row_num_in_sheet}: Extrahiere Daten aus Artikel: {article_page.url}") - wiki_data = self.wiki_scraper.extract_company_data(article_page.url) - else: - debug_print(f"Zeile {row_num_in_sheet}: Kein passender Wikipedia Artikel gefunden.") - # Setze Standard-k.A.-Werte für wiki_data - wiki_data = {'url': 'Kein Artikel gefunden', 'first_paragraph': 'k.A.', 'branche': 'k.A.', 'umsatz': 'k.A.', 'mitarbeiter': 'k.A.', 'categories': 'k.A.'} + if article_page: + debug_print(f" -> Artikel gefunden durch Suche: {article_page.url}") + new_wiki_data_extracted = self.wiki_scraper.extract_company_data(article_page.url) + else: + debug_print(f" -> Kein passender Wikipedia Artikel durch Suche gefunden.") + new_wiki_data_extracted = {'url': 'Kein Artikel gefunden', 'first_paragraph': 'k.A.', 'branche': 'k.A.', 'umsatz': 'k.A.', 'mitarbeiter': 'k.A.', 'categories': 'k.A.'} - # Füge Wiki-Daten zu Updates hinzu - updates.append({'range': f'M{row_num_in_sheet}', 'values': [[wiki_data.get('url', 'k.A.')]]}) - updates.append({'range': f'N{row_num_in_sheet}', 'values': [[wiki_data.get('first_paragraph', 'k.A.')]]}) - updates.append({'range': f'O{row_num_in_sheet}', 'values': [[wiki_data.get('branche', 'k.A.')]]}) - updates.append({'range': f'P{row_num_in_sheet}', 'values': [[wiki_data.get('umsatz', 'k.A.')]]}) - updates.append({'range': f'Q{row_num_in_sheet}', 'values': [[wiki_data.get('mitarbeiter', 'k.A.')]]}) - updates.append({'range': f'R{row_num_in_sheet}', 'values': [[wiki_data.get('categories', 'k.A.')]]}) - # Setze Wiki Timestamp (AN) - updates.append({'range': f'AN{row_num_in_sheet}', 'values': [[now_timestamp]]}) - # Version wird am Ende gesetzt + # --- Updates für Wiki-Spalten vorbereiten --- + if new_wiki_data_extracted: + wiki_data = new_wiki_data_extracted # Überschreibe alte Daten für nachfolgende Schritte + updates.append({'range': f'M{row_num_in_sheet}', 'values': [[wiki_data.get('url', 'k.A.')]]}) + updates.append({'range': f'N{row_num_in_sheet}', 'values': [[wiki_data.get('first_paragraph', 'k.A.')]]}) + updates.append({'range': f'O{row_num_in_sheet}', 'values': [[wiki_data.get('branche', 'k.A.')]]}) + updates.append({'range': f'P{row_num_in_sheet}', 'values': [[wiki_data.get('umsatz', 'k.A.')]]}) + updates.append({'range': f'Q{row_num_in_sheet}', 'values': [[wiki_data.get('mitarbeiter', 'k.A.')]]}) + updates.append({'range': f'R{row_num_in_sheet}', 'values': [[wiki_data.get('categories', 'k.A.')]]}) + updates.append({'range': f'AN{row_num_in_sheet}', 'values': [[now_timestamp]]}) # Setze AN Timestamp neu + # Wenn der Trigger "X (URL Copied)" war, setze S zurück + if status_s_indicates_reparse: + konsistenz_s_idx = COLUMN_MAP.get("Chat Wiki Konsistenzprüfung") + if konsistenz_s_idx is not None: + s_col_letter = self.sheet_handler._get_col_letter(konsistenz_s_idx + 1) + updates.append({'range': f'{s_col_letter}{row_num_in_sheet}', 'values': [["?"]]}) # Setze auf "?" für erneute Prüfung + debug_print(f" -> Status S zurückgesetzt auf '?' für erneute Verifikation.") - elif process_wiki: # Wenn nicht benötigt, aber Modus aktiv ist - debug_print(f"Zeile {row_num_in_sheet}: Überspringe Wikipedia Verarbeitung (Timestamp AN vorhanden).") - # Lade vorhandene Wiki-Daten aus der Zeile, um sie für ChatGPT verfügbar zu machen - wiki_data['url'] = get_cell_value("Wiki URL") or 'k.A.' - wiki_data['first_paragraph'] = get_cell_value("Wiki Absatz") or 'k.A.' - wiki_data['branche'] = get_cell_value("Wiki Branche") or 'k.A.' - wiki_data['umsatz'] = get_cell_value("Wiki Umsatz") or 'k.A.' - wiki_data['mitarbeiter'] = get_cell_value("Wiki Mitarbeiter") or 'k.A.' - wiki_data['categories'] = get_cell_value("Wiki Kategorien") or 'k.A.' - else: # Wenn Modus inaktiv, setze leere Daten - wiki_data = {'url': 'k.A.', 'first_paragraph': 'k.A.', 'branche': 'k.A.', 'umsatz': 'k.A.', 'mitarbeiter': 'k.A.', 'categories': 'k.A.'} + elif process_wiki: + debug_print(f"Zeile {row_num_in_sheet}: Überspringe Wikipedia Verarbeitung (Timestamp AN vorhanden UND S != 'X (URL Copied)').") + # else: Wiki-Verarbeitung nicht aktiv + # --- 3. ChatGPT Evaluationen (Branch etc.) --- + # Trigger: AO fehlt ODER Wiki wurde gerade neu geparsed (um Branch neu zu bewerten) + chat_ts_ao_missing = not get_cell_value("Timestamp letzte Prüfung").strip() + run_chat_eval = process_chatgpt and (chat_ts_ao_missing or reparse_wiki_needed) # reparse_wiki_needed ist True wenn Wiki neu gemacht wurde - # --- 3. ChatGPT Evaluationen --- - chat_ts_needed = process_chatgpt and not get_cell_value("Timestamp letzte Prüfung").strip() + if run_chat_eval: + debug_print(f"Zeile {row_num_in_sheet}: Starte ChatGPT Evaluationen (Grund: AO fehlt? {chat_ts_ao_missing}, Wiki neu geparsed? {reparse_wiki_needed})...") + any_processing_done = True # Markiere, dass etwas getan wurde - if chat_ts_needed: - debug_print(f"Zeile {row_num_in_sheet}: Starte ChatGPT Evaluationen (Timestamp AO fehlt)...") - any_processing_done = True - - # 3.1 Branchenevaluierung (Nutzt aktuelle wiki_data und website_summary) + # 3.1 Branchenevaluierung (Nutzt die ggf. gerade aktualisierten wiki_data) branch_result = evaluate_branche_chatgpt( - crm_branche, - crm_beschreibung, - wiki_data.get('branche', 'k.A.'), - wiki_data.get('categories', 'k.A.'), - website_summary # Nutzt den Wert, der ggf. oben aktualisiert wurde + crm_branche, crm_beschreibung, + wiki_data.get('branche', 'k.A.'), # Nimmt aktuelle wiki_data + wiki_data.get('categories', 'k.A.'),# Nimmt aktuelle wiki_data + website_summary # Nimmt aktuellen website_summary ) updates.append({'range': f'W{row_num_in_sheet}', 'values': [[branch_result.get('branch', 'Fehler')]]}) updates.append({'range': f'X{row_num_in_sheet}', 'values': [[branch_result.get('consistency', 'Fehler')]]}) updates.append({'range': f'Y{row_num_in_sheet}', 'values': [[branch_result.get('justification', 'Fehler')]]}) - # --- HIER weitere ChatGPT-basierte Evaluationen einfügen --- - # Beispiel: FSM-Eignung - # fsm_result = evaluate_fsm_suitability(company_name, wiki_data) - # if fsm_result: - # updates.append({'range': f'Z{row_num_in_sheet}', 'values': [[fsm_result.get('suitability', 'k.A.')]]}) - # updates.append({'range': f'AA{row_num_in_sheet}', 'values': [[fsm_result.get('justification', 'k.A.')]]}) - - # Beispiel: Mitarbeiter-Schätzung etc. - # ... + # --- Hier weitere ChatGPT Evaluationen einfügen --- + # ... (FSM, Mitarbeiter etc.) ... # Setze Timestamp letzte Prüfung (AO) updates.append({'range': f'AO{row_num_in_sheet}', 'values': [[now_timestamp]]}) - # Version wird am Ende gesetzt elif process_chatgpt: - debug_print(f"Zeile {row_num_in_sheet}: Überspringe ChatGPT Evaluationen (Timestamp AO vorhanden).") + debug_print(f"Zeile {row_num_in_sheet}: Überspringe ChatGPT Evaluationen (Timestamp AO vorhanden UND Wiki nicht neu geparsed).") # --- 4. Abschließende Updates --- - # Setze Version, wenn *irgendetwas* in dieser Zeile verarbeitet wurde if any_processing_done: - updates.append({'range': f'AP{row_num_in_sheet}', 'values': [[Config.VERSION]]}) + updates.append({'range': f'AP{row_num_in_sheet}', 'values': [[Config.VERSION]]}) # Setze Version nur wenn etwas getan wurde # --- 5. Batch Update für diese Zeile durchführen --- if updates: - # Führe Batch Update über den Handler aus success = self.sheet_handler.batch_update_cells(updates) - if success: - debug_print(f"Zeile {row_num_in_sheet}: Batch-Update erfolgreich ({len(updates)} Zellen/Bereiche).") - else: - debug_print(f"Zeile {row_num_in_sheet}: FEHLER beim Batch-Update.") + if success: debug_print(f"Zeile {row_num_in_sheet}: Batch-Update erfolgreich ({len(updates)} Zellen/Bereiche).") + else: debug_print(f"Zeile {row_num_in_sheet}: FEHLER beim Batch-Update.") else: - debug_print(f"Zeile {row_num_in_sheet}: Keine Updates zum Schreiben (alles übersprungen oder keine Änderungen).") + debug_print(f"Zeile {row_num_in_sheet}: Keine Updates zum Schreiben.") debug_print(f"--- Verarbeitung für Zeile {row_num_in_sheet} abgeschlossen ---") - # Kurze Pause (optional, aber gut für APIs) - time.sleep(max(0.2, Config.RETRY_DELAY / 10)) + time.sleep(max(0.1, Config.RETRY_DELAY / 20)) # Kürzere Pause, da Haupt-API Calls schon Pausen haben def process_rows_sequentially(self, start_row_index, num_rows_to_process, process_wiki=True, process_chatgpt=True, process_website=True):