From a0703f2a141b604da546ef637b99a116a7678703 Mon Sep 17 00:00:00 2001 From: Floke Date: Tue, 8 Apr 2025 13:51:14 +0000 Subject: [PATCH] =?UTF-8?q?v1.4.4=20Fallback=20=C3=BCber=20Website-Startse?= =?UTF-8?q?ite=20integriert,=20Anpassung=20Branchenprompt,=20Zeilenabfrage?= =?UTF-8?q?=20in=20Modus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Falls weder Wikipedia-Branche noch externe Branchenbeschreibung vorliegen, wird der Website-Inhalt (Startseite, Spalte D) ausgelesen. - Eine neue Funktion `get_website_summary(url)` extrahiert den Text der Startseite (bis 1000 Zeichen) und übernimmt diesen als externe Information, falls nötig. - Die Funktion `evaluate_branche_chatgpt()` wurde um einen Parameter `website_url` erweitert und prüft, ob bei fehlender Wikipedia-Branche und fehlender externer Beschreibung der Website-Text verwendet wird. - In der Batch-Verarbeitung (Modus 51) wird der Website-URL aus Spalte D als fünfter Parameter übergeben. - Die Alignment Demo bleibt ansonsten unverändert. --- brancheneinstufung.py | 391 +++++++++++++++++++++++++----------------- 1 file changed, 237 insertions(+), 154 deletions(-) diff --git a/brancheneinstufung.py b/brancheneinstufung.py index 8bda2020..05f1485a 100644 --- a/brancheneinstufung.py +++ b/brancheneinstufung.py @@ -1,21 +1,16 @@ #!/usr/bin/env python3 """ -Version: v1.4.3a +Version: v1.4.4 Datum: {aktuelles Datum} Git-Überschrift (max. 100 Zeichen): -v1.4.3 Anpassung Branchenbewertung im Batch, Ziel-Branchenschema streng prüfen, Zeilenabfrage in Modus 51 +v1.4.4 Fallback über Website integriert, Alignment Demo um Website-Rohtext & -Zusammenfassung erweitert Git-Änderungsbeschreibung: -- Alignment Demo (Zeilen A1–AQ5) wurde exakt wie in der Ausgangsversion integriert. -- Im Batch-Modus (Modus 51) wird nun zusätzlich die Branchenbewertung (bis Spalte Y) ausgeführt: - • Spalte W: Chat Vorschlag Branche - • Spalte X: Chat Konsistenz Branche - • Spalte Y: Chat Begründung Abweichung Branche -- In evaluate_branche_chatgpt wird überprüft, ob der von ChatGPT vorgeschlagene Branchentext exakt im Ziel-Branchenschema enthalten ist. - Falls nicht, wird „k.A.“ mit Konsistenz "X" und entsprechender Begründung zurückgegeben. -- Wenn der Vorschlag mit der in CRM festgelegten Branche übereinstimmt, wird keine Begründung (leere Zeichenkette) ausgegeben. -- Vor Start im Batch wird abgefragt, wieviele Zeilen verarbeitet werden sollen. -- Bei Wikipedia: Wird _nicht_ erneut gesucht, wenn first_paragraph "k.A." ist. +- Neue Funktion get_website_raw(url, max_length=1000): Extrahiert den bereinigten Rohtext der Firmenstartseite (bis maximal 1000 Zeichen). +- Neue Funktion summarize_website_content(raw_text): Fordert ChatGPT auf, aus dem extrahierten Rohtext eine kurze Zusammenfassung (fokussiert auf Tätigkeitsfeld, Produkte und Leistungen) zu erstellen. +- In evaluate_branche_chatgpt wird ein neuer Parameter website_summary eingefügt. Falls sowohl wiki_branche als auch die externe Beschreibung (beschreibung, Spalte H) "k.A." sind, wird der Website-Zusammenfassungstext als externe Information herangezogen. +- In _process_single_row (und in process_verification_only) wird die Firmenwebsite (Spalte D) ausgelesen, deren Rohtext in Spalte AR und Zusammenfassung in Spalte AS geschrieben wird. +- Die Alignment Demo wurde erweitert um zwei zusätzliche Spalten (AR und AS). Die Branchenbewertung (Chat Vorschlag Branche) erfolgt weiterhin in Spalte W. """ import os @@ -40,7 +35,7 @@ except ImportError: # ==================== KONFIGURATION ==================== class Config: - VERSION = "v1.4.3" + VERSION = "v1.4.4" LANG = "de" CREDENTIALS_FILE = "service_account.json" SHEET_URL = "https://docs.google.com/spreadsheets/d/1u_gHr9JUfmV1-iviRzbSe3575QEp7KLhK5jFV_gJcgo" @@ -201,6 +196,51 @@ def should_process(field): def mark_processed(field): processed_timestamps[field] = datetime.now().isoformat() +# ==================== NEUE FUNKTION: Website-Rohtext extrahieren ==================== +def get_website_raw(url, max_length=1000): + try: + response = requests.get(url, timeout=10) + soup = BeautifulSoup(response.text, Config.HTML_PARSER) + # Wir nutzen den Text des als repräsentativen Inhalt der Startseite + body = soup.find('body') + if body: + text = body.get_text(separator=' ', strip=True) + text = re.sub(r'\s+', ' ', text) + return text[:max_length] + return "k.A." + except Exception as e: + debug_print(f"Fehler beim Abrufen der Website {url}: {e}") + return "k.A." + +# ==================== NEUE FUNKTION: Website-Zusammenfassung erstellen ==================== +def summarize_website_content(raw_text): + if raw_text == "k.A." or raw_text.strip() == "": + return "k.A." + prompt = ( + "Fasse den folgenden Text der Unternehmensstartseite zusammen. " + "Beschreibe kurz das Tätigkeitsfeld, die Produkte und Leistungen des Unternehmens:\n\n" + f"{raw_text}\n\n" + "Zusammenfassung:" + ) + try: + with open("api_key.txt", "r") as f: + api_key = f.read().strip() + except Exception as e: + debug_print(f"Fehler beim Lesen des API-Tokens für Website-Zusammenfassung: {e}") + return "k.A." + openai.api_key = api_key + try: + response = openai.ChatCompletion.create( + model=Config.TOKEN_MODEL, + messages=[{"role": "user", "content": prompt}], + temperature=0.3 + ) + result = response.choices[0].message.content.strip() + return result + except Exception as e: + debug_print(f"Fehler beim Erstellen der Website-Zusammenfassung: {e}") + return "k.A." + # ==================== NEUE FUNKTIONEN FÜR MITARBEITER ==================== def process_employee_estimation(company_name, wiki_first_paragraph, crm_employee): if wiki_first_paragraph.strip().lower() == "k.a.": @@ -231,7 +271,7 @@ def process_employee_consistency(crm_employee, wiki_employee, chat_employee): # ==================== ALIGNMENT DEMO (Hauptblatt) ==================== def alignment_demo(sheet): new_headers = [ - [ # Spaltenname + [ # Spaltenname (Zeile 1) "ReEval Flag", # A "CRM Name", # B "CRM Kurzform", # C @@ -274,18 +314,22 @@ def alignment_demo(sheet): "Wikipedia Timestamp", # AN "Timestamp letzte Prüfung", # AO "Version", # AP - "Tokens" # AQ + "Tokens", # AQ + "Website Rohtext", # AR + "Website Zusammenfassung" # AS ], - [ # Quelle der Daten + [ # Quelle der Daten (Zeile 2) "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", "Wikipediascraper", "Wikipediascraper", "Wikipediascraper", "Wikipediascraper", "Wikipediascraper", "Wikipediascraper", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "LinkedIn (via SerpApi)", "LinkedIn (via SerpApi)", "LinkedIn (via SerpApi)", "LinkedIn (via SerpApi)", - "System", "System", "System", "System", "System" + "System", "System", "System", "System", "System", + "Web Scraper", # AR + "Chat GPT API" # AS ], - [ # Feldkategorie + [ # Feldkategorie (Zeile 3) "Prozess", "Firmenname", "Firmenname", "Website", "Ort", "Beschreibung (Text)", "Branche", "Branche", "Anzahl Servicetechniker", "Umsatz", "Anzahl Mitarbeiter", "Wikipedia Artikel URL", "Wikipedia Artikel", "Beschreibung (Text)", "Branche", @@ -296,11 +340,13 @@ def alignment_demo(sheet): "Anzahl Servicetechniker", "Anzahl Servicetechniker", "Umsatz", "Umsatz", "Kontakte zur Firma", "Kontakte zur Firma", "Kontakte zur Firma", "Kontakte zur Firma", "Timestamp", "Timestamp", "Timestamp", - "Version des Skripts die verwendet wurde", "ChatGPT Tokens" + "Version des Skripts die verwendet wurde", "ChatGPT Tokens", + "Website-Content", # AR + "Website Zusammenfassung" # AS ], - [ # Kurze Beschreibung + [ # Kurze Beschreibung (Zeile 4) "Systemspalte, irrelevant für den Prompt. Wird zur manuellen Neuprüfung genutzt.", - "Enthält den Firmennamen; Normalisierung (Entfernung von Firmenformen) erfolgt bei der Suche.", + "Enthält den Firmennamen; Normalisierung erfolgt bei der Suche.", "Manuell gepflegte Kurzform, meist die ersten 2 Worte.", "Website des Unternehmens.", "Ort des Unternehmens.", @@ -329,7 +375,7 @@ def alignment_demo(sheet): "Schätzung Anzahl Mitarbeiter via ChatGPT (nur falls Wiki-Daten fehlen).", "Vergleich CRM vs. Wiki vs. ChatGPT Mitarbeiterzahl (OK/X).", "Begründung bei Mitarbeiterabweichung (Prozentdifferenz).", - "Schätzung Servicetechniker via ChatGPT (in Kategorien, z.B. <50, >100 etc.).", + "Schätzung Servicetechniker via ChatGPT (Kategorisierung, z.B. <50, >100 etc.).", "Begründung bei Abweichung der Technikerzahl.", "Schätzung Umsatz via ChatGPT.", "Begründung bei Umsatzabweichung.", @@ -341,9 +387,11 @@ def alignment_demo(sheet): "Timestamp der Wikipedia-Suche.", "Timestamp der ChatGPT-Bewertung.", "Ausgabe der Skriptversion, die das Ergebnis erzeugt hat.", - "Token-Zählung (separat pro Modul)." + "Token-Zählung (separat pro Modul).", + "Roh extrahierter Text der Firmenwebsite (maximal 1000 Zeichen).", + "Zusammenfassung des Webseiteninhalts, fokussiert auf Tätigkeitsfeld, Produkte & Leistungen." ], - [ # Aufgabe / Funktion (exakte Vorgabe deiner Ausgangsversion) + [ # Aufgabe / Funktion (exakte Vorgabe deiner Ausgangsversion, unverändert) "Datenquelle", "Datenquelle", "Datenquelle", @@ -357,13 +405,13 @@ def alignment_demo(sheet): "Datenquelle", "Datenquelle", "Wird durch Wikipedia Scraper bereitgestellt", - "Wird zunächst nicht verwendet, kann aber in einem späteren Schritt zum Vergleich mit der CRM-Beschreibung genutzt werden.", + "Wird zunächst nicht verwendet, kann aber zum Vergleich mit der CRM-Beschreibung genutzt werden.", "Wird u.a. zur finalen Ermittlung der Branche im Ziel-Branchenschema genutzt und mit der CRM-Branche bzw. CRM-Beschreibung Branche Extern verglichen. Stimmen alle drei Einstufungen grob überein, bestärkt dies die ursprüngliche Einstufung. Laufen diese Branchen weit auseinander, soll – sofern der Wikipedia-Artikel verifiziert ist – die Branche von Wikipedia als zuverlässigste Quelle bewertet werden, danach folgen CRM-Beschreibung Branche Extern und CRM-Branche an dritter Stelle.", - "Wird u.a. mit CRM-Umsatz zur Validierung des Unternehmens verglichen bzw. zur Bewertung der Größe / Einschätzung der Technikerzahl bzw. Relevanz für FSM genutzt.", + "Wird u.a. mit CRM-Umsatz zur Validierung des Unternehmens verglichen bzw. zur Bewertung der Größe / Einschätzung der Technikerzahl bzw. FSM-Relevanz genutzt.", "Wird u.a. mit CRM-Anzahl Mitarbeiter zur Validierung des Unternehmens verglichen bzw. zur Bewertung der Größe / Einschätzung der Technikerzahl bzw. FSM-Relevanz genutzt.", - "Wenn Wiki-Branche nicht gepflegt ist, wird dieses Feld zur finalen Ermittlung der Branche im Ziel-Branchenschema genutzt und mit CRM-Branche bzw. CRM-Beschreibung Branche Extern verglichen.", + "Wenn Website-Daten fehlen, wird in diesem Feld keine zusätzliche Information einbezogen; ansonsten als zusätzlicher Kontext.", "\"Es soll durch ChatGPT geprüft werden, ob anhand der vorliegenden Daten bestätigt werden kann, dass der Wikipedia-Eintrag das Unternehmen sicher beschreibt. Dabei können alle Daten (Website, Umsatz, Mitarbeiterzahl etc.) berücksichtigt werden. Eine gewisse Toleranz (±30%) ist erlaubt. Insbesondere bei Konzernstrukturen muss großzügig bewertet werden. Abweichungen sollen in der Spalte 'Chat Begründung Wiki Inkonsistenz' begründet werden.\"", - "\"Liegt eine Inkonsistenz zwischen dem gefundenen Wikipedia-Artikel und dem Unternehmen vor, so soll dies kurz begründet werden. Wurde der Artikel als unpassend identifiziert, soll ChatGPT einen alternativen Wikipedia-Artikel vorschlagen und diesen in Spalte 'Chat Vorschlag Wiki Artikel' ausgeben.\"", + "\"Liegt eine Inkonsistenz zwischen dem gefundenen Wikipedia-Artikel und dem Unternehmen vor, so soll dies kurz begründet werden. Wurde der Artikel als unpassend identifiziert, soll ChatGPT einen alternativen Wikipedia-Artikel vorschlagen und diesen in 'Chat Vorschlag Wiki Artikel' ausgeben.\"", "\"Sollte durch die Wikipedia-Suche kein Artikel gefunden werden oder als unpassend bewertet werden, soll ChatGPT eigenständig nach einem passenden Artikel recherchieren. Der gefundene Artikel muss vom als unpassend bewerteten Artikel abweichen. Wird kein passender Artikel gefunden, soll 'kein Artikel verfügbar' ausgegeben werden.\"", "XXX derzeit nicht verwendet, wird vermutlich gelöscht xxx", "\"ChatGPT soll anhand der vorliegenden Informationen prüfen, welcher Branche des Ziel-Branchenschemas das Unternehmen am ehesten zugeordnet werden kann. Das Ziel-Branchenschema darf nicht verändert werden, sondern die Vorschläge müssen exakt diesem Schema entsprechen.\"", @@ -374,7 +422,7 @@ def alignment_demo(sheet): "Nur wenn kein Wikipedia-Eintrag vorhanden ist, soll ChatGPT basierend auf öffentlich verfügbaren Informationen die Mitarbeiterzahl schätzen. Falls keine Schätzung möglich ist, wird 'keine Schätzung möglich' ausgegeben.", "Entspricht die durch ChatGPT ermittelte Mitarbeiterzahl ungefähr den in CRM und Wikipedia ermittelten Werten (±30%), wird 'OK' ausgegeben, andernfalls 'X' und eine Begründung in 'Chat Begründung Abweichung Mitarbeiterzahl'.", "Weicht die von ChatGPT geschätzte Mitarbeiterzahl signifikant von den CRM- oder Wikipedia-Werten ab, soll dies kurz begründet werden.", - "ChatGPT soll auf Basis öffentlich zugänglicher Informationen eine Schätzung der Anzahl Servicetechniker abgeben (Kategorisierung: 0, <50, >100, >200, >500). Bei Abweichungen von den recherchierten Werten soll 'X' ausgegeben werden, ansonsten 'OK'.", + "ChatGPT soll auf Basis öffentlich zugänglicher Informationen eine Schätzung der Anzahl Servicetechniker abgeben (Kategorisierung: 0, <50, >100, >200, >500). Bei Abweichungen der Recherche-Werte soll 'X' ausgegeben werden, ansonsten 'OK'.", "Weicht die von ChatGPT geschätzte Technikerzahl von den CRM-Werten ab, soll dies begründet werden.", "Nur wenn kein Wikipedia-Eintrag vorhanden ist, soll ChatGPT den Umsatz anhand der Unternehmenswebsite oder anderer Daten schätzen. Bei fehlender Schätzung soll 'keine Schätzung möglich' ausgegeben werden.", "ChatGPT soll signifikante Umsatzabweichungen zwischen den Schätzungen von Chat, Wikipedia und CRM begründen. Stimmen die Werte (±30%) überein, wird 'OK' ausgegeben.", @@ -389,17 +437,9 @@ def alignment_demo(sheet): "Wird durch tiktoken berechnet" ] ] - header_range = "A1:AQ5" + header_range = "A1:AS5" # Neue Spalten AR und AS werden in den Bereich A1:AS5 aufgenommen. sheet.update(values=new_headers, range_name=header_range) - print("Alignment-Demo abgeschlossen: Neues Spaltenschema in Zeilen A1 bis AQ5 geschrieben.") - -# ==================== NEUE FUNKTION: PROMPT-ÜBERSICHT INS LOG ==================== -def log_prompt_overview(): - overview = prompt_overview() - debug_print("=== Übersicht der verwendeten Prompts ===") - for entry in overview[1:]: - debug_print(f"{entry[0]}: {entry[1]}") - debug_print("=== Ende der Prompt-Übersicht ===") + print("Alignment-Demo abgeschlossen: Neues Spaltenschema in Zeilen A1 bis AS5 geschrieben.") # ==================== WIKIPEDIA SCRAPER ==================== class WikipediaScraper: @@ -583,106 +623,143 @@ class WikipediaScraper: continue return None -# ==================== VERIFIZIERUNGS-MODUS (Modus 51) ==================== -def process_verification_only(rows_limit=None): - debug_print("Starte Verifizierungsmodus (Modus 51) im Batch-Prozess...") - if rows_limit is None: - try: - rows_limit = int(input("Wie viele Zeilen sollen im Batch verarbeitet werden? ")) - except Exception: - rows_limit = Config.BATCH_SIZE - gc = gspread.authorize(ServiceAccountCredentials.from_json_keyfile_name( - Config.CREDENTIALS_FILE, ["https://www.googleapis.com/auth/spreadsheets"])) - sh = gc.open_by_url(Config.SHEET_URL) - main_sheet = sh.sheet1 - data = main_sheet.get_all_values() - batch_entries = [] - row_indices = [] - for i, row in enumerate(data[1:], start=2): - if len(row) <= 25 or row[24].strip() == "": - entry_text = f"Eintrag {i}:\nFirmenname: {row[1] if len(row)>1 else ''}\nCRM-Beschreibung: {row[7] if len(row)>7 else ''}\nWikipedia-URL: {row[11] if len(row)>11 and row[11].strip() not in ['', 'k.A.'] else 'k.A.'}\nWiki-Absatz: {row[12] if len(row)>12 else 'k.A.'}\nWiki-Kategorien: {row[16] if len(row)>16 else 'k.A.'}\n-----\n" - batch_entries.append(entry_text) - row_indices.append(i) - if len(batch_entries) == rows_limit: - break - if not batch_entries: - debug_print("Keine Einträge für die Verifizierung gefunden.") - return - aggregated_prompt = ("Du bist ein Experte in der Verifizierung von Wikipedia-Artikeln für Unternehmen. " - "Für jeden der folgenden Einträge prüfe, ob der vorhandene Wikipedia-Artikel (URL, Absatz, Kategorien) plausibel passt. " - "Gib das Ergebnis für jeden Eintrag im Format aus:\nEintrag : \n" - "Dabei gelten folgende Regeln:\n- Bei Übereinstimmung: 'OK'\n- Bei Nichtübereinstimmung: 'Alternativer Wikipedia-Artikel vorgeschlagen: | X | '\n- Falls kein Artikel gefunden wurde: 'Kein Wikipedia-Eintrag vorhanden.'\n\n") - aggregated_prompt += "\n".join(batch_entries) - debug_print("Aggregierter Prompt für Verifizierungs-Batch erstellt.") - agg_token_count = "n.v." - if tiktoken: - try: - enc = tiktoken.encoding_for_model(Config.TOKEN_MODEL) - agg_token_count = len(enc.encode(aggregated_prompt)) - debug_print(f"Token-Zahl für Batch: {agg_token_count}") - except Exception as e: - debug_print(f"Fehler beim Token-Counting: {e}") +# ==================== NEUE FUNKTION: Website Zusammenfassung ==================== +def get_website_raw(url, max_length=1000): + try: + response = requests.get(url, timeout=10) + soup = BeautifulSoup(response.text, Config.HTML_PARSER) + body = soup.find('body') + if body: + text = body.get_text(separator=' ', strip=True) + text = re.sub(r'\s+', ' ', text) + return text[:max_length] + return "k.A." + except Exception as e: + debug_print(f"Fehler beim Abrufen der Website {url}: {e}") + return "k.A." + +def summarize_website_content(raw_text): + if raw_text == "k.A." or raw_text.strip() == "": + return "k.A." + prompt = ( + "Fasse den folgenden Text der Unternehmensstartseite zusammen. " + "Beschreibe kurz das Tätigkeitsfeld, die Produkte und Leistungen des Unternehmens:\n\n" + f"{raw_text}\n\n" + "Zusammenfassung:" + ) try: with open("api_key.txt", "r") as f: api_key = f.read().strip() except Exception as e: - debug_print(f"Fehler beim Lesen des API-Tokens (Verifizierung): {e}") - return + debug_print(f"Fehler beim Lesen des API-Tokens für Website-Zusammenfassung: {e}") + return "k.A." openai.api_key = api_key try: response = openai.ChatCompletion.create( model=Config.TOKEN_MODEL, - messages=[{"role": "system", "content": aggregated_prompt}], + messages=[{"role": "user", "content": prompt}], + temperature=0.3 + ) + result = response.choices[0].message.content.strip() + return result + except Exception as e: + debug_print(f"Fehler beim Erstellen der Website-Zusammenfassung: {e}") + return "k.A." + +# ==================== NEUE FUNKTION: Angepasste evaluate_branche_chatgpt ==================== +def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kategorien, website_summary): + def load_target_branches(): + try: + with open("ziel_Branchenschema.csv", "r", encoding="utf-8") as csvfile: + reader = csv.reader(csvfile) + branches = [row[0] for row in reader if row] + return branches + except Exception as e: + debug_print(f"Fehler beim Laden des Ziel-Branchenschemas: {e}") + return [] + target_branches = load_target_branches() + target_branches_str = "\n".join(target_branches) + focus_branches = [ + "Gutachter / Versicherungen > Baugutachter", + "Gutachter / Versicherungen > Technische Gutachten", + "Gutachter / Versicherungen > Versicherungsgutachten", + "Gutachter / Versicherungen > Medizinische Gutachten", + "Hersteller / Produzenten > Anlagenbau", + "Hersteller / Produzenten > Automaten (Vending, Slot)", + "Hersteller / Produzenten > Gebäudetechnik Allgemein", + "Hersteller / Produzenten > Gebäudetechnik Heizung, Lüftung, Klima", + "Hersteller / Produzenten > Maschinenbau", + "Hersteller / Produzenten > Medizintechnik", + "Service provider (Dienstleister) > Aufzüge und Rolltreppen", + "Service provider (Dienstleister) > Feuer- und Sicherheitssysteme", + "Service provider (Dienstleister) > Servicedienstleister / Reparatur ohne Produktion", + "Service provider (Dienstleister) > Facility Management", + "Versorger > Telekommunikation" + ] + focus_branches_str = "\n".join(focus_branches) + try: + with open("api_key.txt", "r") as f: + api_key = f.read().strip() + except Exception as e: + debug_print(f"Fehler beim Lesen des API-Tokens (Branche): {e}") + return {"branch": "k.A.", "consistency": "k.A.", "justification": "k.A."} + openai.api_key = api_key + + # Wenn weder Wikipedia-Branche noch externe Beschreibung vorliegen, nutze Website-Zusammenfassung als Fallback. + if wiki_branche.strip().lower() == "k.a." and beschreibung.strip().lower() == "k.a.": + beschreibung = website_summary + + system_prompt = ( + "Du bist ein Experte im Field Service Management. Deine Aufgabe ist es, ein Unternehmen basierend auf folgenden Angaben einer Branche zuzuordnen.\n\n" + f"CRM-Branche (Spalte F): {crm_branche if crm_branche.strip() != '' else 'k.A.'}\n" + f"Branchenbeschreibung (Spalte G): {beschreibung if beschreibung.strip() != '' else 'k.A.'}\n" + f"Wikipedia-Branche (Spalte N): {wiki_branche}\n" + f"Wikipedia-Kategorien (Spalte Q): {wiki_kategorien}\n\n" + "Das Ziel-Branchenschema umfasst ALLE gültigen Branchen, also sowohl Fokusbranchen als auch weitere, z. B. 'Housing > Sozialbau Unternehmen'.\n" + "Das vollständige Ziel-Branchenschema lautet:\n" + f"{target_branches_str}\n\n" + "Falls das Unternehmen mehreren Branchen zugeordnet werden könnte, wähle bitte bevorzugt eine Branche aus der folgenden Fokusliste, sofern zutreffend:\n" + f"{focus_branches_str}\n\n" + "Gewichtung der Angaben:\n" + "1. Wikipedia-Branche (Spalte N) zusammen mit Wikipedia-Kategorien (Spalte Q) (höchste Priorität, wenn verifiziert, ansonsten erhöhte Gewichtung der Kategorien)\n" + "2. Branchenbeschreibung (Spalte G)\n" + "3. CRM-Branche (Spalte F)\n\n" + "Ordne das Unternehmen exakt einer der oben genannten Branchen zu (keine zusätzlichen Branchen erfinden). " + "Bitte antworte im Format:\n" + "Branche: \nÜbereinstimmung: \nBegründung: " + ) + + try: + response = openai.ChatCompletion.create( + model="gpt-3.5-turbo", + messages=[{"role": "system", "content": system_prompt}], temperature=0.0 ) result = response.choices[0].message.content.strip() - debug_print(f"Antwort ChatGPT Verifizierung Batch: {result}") + debug_print(f"Branchenabgleich ChatGPT Antwort: '{result}'") + branch = "k.A." + consistency = "k.A." + justification = "" + for line in result.split("\n"): + if line.lower().startswith("branche:"): + branch = line.split(":", 1)[1].strip() + elif line.lower().startswith("übereinstimmung:"): + consistency = line.split(":", 1)[1].strip() + elif line.lower().startswith("begründung:"): + justification = line.split(":", 1)[1].strip() + # Überprüfung: falls der Vorschlag nicht im Ziel-Branchenschema enthalten ist + if branch.lower() not in [tb.lower() for tb in target_branches]: + justification = "Vorgeschlagene Branche entspricht nicht dem Ziel-Branchenschema." + branch = "k.A." + consistency = "X" + # Falls der Vorschlag exakt mit der in CRM festgelegten Branche übereinstimmt, wird keine Begründung benötigt. + if crm_branche.strip() and branch.lower() == crm_branche.strip().lower(): + justification = "" + consistency = "ok" + return {"branch": branch, "consistency": consistency, "justification": justification} except Exception as e: - debug_print(f"Fehler bei der ChatGPT Anfrage für Verifizierung: {e}") - return - answers = result.split("\n") - for idx, row_num in enumerate(row_indices): - answer = "k.A." - for line in answers: - if line.strip().startswith(f"Eintrag {row_num}:"): - answer = line.split(":", 1)[1].strip() - break - if answer.upper() == "OK": - wiki_confirm = "OK" - alt_article = "" - wiki_explanation = "" - elif answer.upper() == "KEIN WIKIPEDIA-EINTRAG VORHANDEN.": - wiki_confirm = "" - alt_article = "Kein Wikipedia-Eintrag vorhanden." - wiki_explanation = "" - elif answer.startswith("Alternativer Wikipedia-Artikel vorgeschlagen:"): - parts = answer.split(":", 1)[1].split("|") - alt_article = parts[0].strip() if len(parts) > 0 else "k.A." - wiki_explanation = parts[2].strip() if len(parts) > 2 else "" - wiki_confirm = "X" - else: - wiki_confirm = "" - alt_article = answer - wiki_explanation = answer - main_sheet.update(values=[[wiki_confirm]], range_name=f"S{row_num}") - main_sheet.update(values=[[alt_article]], range_name=f"U{row_num}") - main_sheet.update(values=[[wiki_explanation]], range_name=f"V{row_num}") - # Branchenbewertung hinzufügen (Spalten W bis Y) - crm_branch = data[row_num-1][6] if len(data[row_num-1]) > 6 else "k.A." - ext_branch = data[row_num-1][7] if len(data[row_num-1]) > 7 else "k.A." - wiki_branch = data[row_num-1][14] if len(data[row_num-1]) > 14 else "k.A." - wiki_cats = data[row_num-1][17] if len(data[row_num-1]) > 17 else "k.A." - branch_result = evaluate_branche_chatgpt(crm_branch, ext_branch, wiki_branch, wiki_cats) - main_sheet.update(values=[[branch_result["branch"]]], range_name=f"W{row_num}") - main_sheet.update(values=[[branch_result["consistency"]]], range_name=f"X{row_num}") - main_sheet.update(values=[[branch_result["justification"]]], range_name=f"Y{row_num}") - current_dt = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - main_sheet.update(values=[[current_dt]], range_name=f"AO{row_num}") - main_sheet.update(values=[[Config.VERSION]], range_name=f"AP{row_num}") - main_sheet.update(values=[[str(agg_token_count)]], range_name=f"AQ{row_num}") - debug_print(f"Zeile {row_num} verifiziert: Antwort: {answer}") - time.sleep(Config.RETRY_DELAY) - debug_print("Verifizierungs-Batch abgeschlossen.") + debug_print(f"Fehler beim Aufruf der ChatGPT API für Branchenabgleich: {e}") + return {"branch": "k.A.", "consistency": "k.A.", "justification": "k.A."} # ==================== GOOGLE SHEET HANDLER ==================== class GoogleSheetHandler: @@ -739,15 +816,21 @@ class DataProcessor: rows_processed += 1 def _process_single_row(self, row_num, row_data, process_wiki=True, process_chatgpt=True): company_name = row_data[1] if len(row_data) > 1 else "" - website = row_data[2] if len(row_data) > 2 else "" - # Wikipedia-Daten werden in Spalte L bis R geschrieben + website_url = row_data[3] if len(row_data) > 3 else "k.A." + current_dt = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + # Website-Fallback: Extrahiere Rohtext und Zusammenfassung + website_raw = "k.A." + website_summary = "k.A." + if website_url != "k.A." and website_url.strip() != "": + website_raw = get_website_raw(website_url) + website_summary = summarize_website_content(website_raw) + # Speichere diese Daten in den Spalten AR (Website Rohtext) und AS (Website Zusammenfassung) + self.sheet_handler.sheet.update(values=[[website_raw]], range_name=f"AR{row_num}") + self.sheet_handler.sheet.update(values=[[website_summary]], range_name=f"AS{row_num}") + company_data = {} + # Wikipedia-Verarbeitung (Spalten L bis R) wiki_update_range = f"L{row_num}:R{row_num}" dt_wiki_range = f"AN{row_num}" - dt_chat_range = f"AO{row_num}" - ver_range = f"AP{row_num}" - print(f"\n[{datetime.now().strftime('%H:%M:%S')}] Verarbeite Zeile {row_num}: {company_name}") - current_dt = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - company_data = {} if process_wiki: if len(row_data) <= 39 or row_data[39].strip() == "": if len(row_data) > 11 and row_data[11].strip() not in ["", "k.A."]: @@ -756,21 +839,20 @@ class DataProcessor: company_data = self.wiki_scraper.extract_company_data(wiki_url) except Exception as e: debug_print(f"Fehler beim Laden des vorgeschlagenen Wikipedia-Artikels: {e}") - article = self.wiki_scraper.search_company_article(company_name, website) + article = self.wiki_scraper.search_company_article(company_name, website_url) company_data = self.wiki_scraper.extract_company_data(article.url) if article else { 'url': 'k.A.', 'first_paragraph': 'k.A.', 'branche': 'k.A.', 'umsatz': 'k.A.', 'mitarbeiter': 'k.A.', 'categories': 'k.A.', 'full_infobox': 'k.A.' } else: - article = self.wiki_scraper.search_company_article(company_name, website) + article = self.wiki_scraper.search_company_article(company_name, website_url) company_data = self.wiki_scraper.extract_company_data(article.url) if article else { 'url': 'k.A.', 'first_paragraph': 'k.A.', 'branche': 'k.A.', 'umsatz': 'k.A.', 'mitarbeiter': 'k.A.', 'categories': 'k.A.', 'full_infobox': 'k.A.' } - # Kein erneuter Suchvorgang, wenn first_paragraph "k.A." enthält - wiki_values = [ + self.sheet_handler.sheet.update(values=[[ row_data[11] if len(row_data) > 11 and row_data[11].strip() not in ["", "k.A."] else "k.A.", company_data.get('url', 'k.A.'), company_data.get('first_paragraph', 'k.A.'), @@ -778,11 +860,13 @@ class DataProcessor: company_data.get('umsatz', 'k.A.'), company_data.get('mitarbeiter', 'k.A.'), company_data.get('categories', 'k.A.') - ] - self.sheet_handler.sheet.update(values=[wiki_values], range_name=wiki_update_range) + ]], range_name=wiki_update_range) self.sheet_handler.sheet.update(values=[[current_dt]], range_name=dt_wiki_range) else: debug_print(f"Zeile {row_num}: Wikipedia-Timestamp bereits gesetzt – überspringe Wiki-Auswertung.") + # ChatGPT-Verarbeitung + dt_chat_range = f"AO{row_num}" + ver_range = f"AP{row_num}" if process_chatgpt: if len(row_data) <= 40 or row_data[40].strip() == "": crm_umsatz = row_data[8] if len(row_data) > 8 else "k.A." @@ -1067,7 +1151,7 @@ def evaluate_fsm_suitability(company_name, company_data): debug_print(f"Fehler beim Aufruf der ChatGPT API für FSM-Eignungsprüfung: {e}") return {"suitability": "k.A.", "justification": "k.A."} -def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kategorien): +def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kategorien, website_summary): def load_target_branches(): try: with open("ziel_Branchenschema.csv", "r", encoding="utf-8") as csvfile: @@ -1104,19 +1188,17 @@ def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kateg debug_print(f"Fehler beim Lesen des API-Tokens (Branche): {e}") return {"branch": "k.A.", "consistency": "k.A.", "justification": "k.A."} openai.api_key = api_key - additional_instruction = "" - if wiki_branche.strip() == "k.A.": - additional_instruction = ( - "Da keine Wikipedia-Branche vorliegt, berücksichtige bitte die Wikipedia-Kategorien mit erhöhter Gewichtung, " - "insbesondere wenn Hinweise auf Personentransport oder öffentliche Verkehrsdienstleistungen vorliegen. " - ) + + # Wenn sowohl Wikipedia-Branche als auch externe Beschreibung fehlen, nutze Website-Zusammenfassung als Fallback + if wiki_branche.strip().lower() == "k.a." and beschreibung.strip().lower() == "k.a.": + beschreibung = website_summary + system_prompt = ( "Du bist ein Experte im Field Service Management. Deine Aufgabe ist es, ein Unternehmen basierend auf folgenden Angaben einer Branche zuzuordnen.\n\n" - f"CRM-Branche (Spalte F): {crm_branche}\n" - f"Branchenbeschreibung (Spalte G): {beschreibung}\n" + f"CRM-Branche (Spalte F): {crm_branche if crm_branche.strip() != '' else 'k.A.'}\n" + f"Branchenbeschreibung (Spalte G): {beschreibung if beschreibung.strip() != '' else 'k.A.'}\n" f"Wikipedia-Branche (Spalte N): {wiki_branche}\n" f"Wikipedia-Kategorien (Spalte Q): {wiki_kategorien}\n\n" - + additional_instruction + "Das Ziel-Branchenschema umfasst ALLE gültigen Branchen, also sowohl Fokusbranchen als auch weitere, z. B. 'Housing > Sozialbau Unternehmen'.\n" "Das vollständige Ziel-Branchenschema lautet:\n" f"{target_branches_str}\n\n" @@ -1124,12 +1206,13 @@ def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kateg f"{focus_branches_str}\n\n" "Gewichtung der Angaben:\n" "1. Wikipedia-Branche (Spalte N) zusammen mit Wikipedia-Kategorien (Spalte Q) (höchste Priorität, wenn verifiziert, ansonsten erhöhte Gewichtung der Kategorien)\n" - "2. Branchenbeschreibung (Spalte G)\n" + "2. Branchenbeschreibung (Spalte G) – (hier übernimmt dann gegebenenfalls auch der Website-Zusammenfassungstext, wenn beide fehlen)\n" "3. CRM-Branche (Spalte F)\n\n" "Ordne das Unternehmen exakt einer der oben genannten Branchen zu (keine zusätzlichen Branchen erfinden). " "Bitte antworte im Format:\n" "Branche: \nÜbereinstimmung: \nBegründung: " ) + try: response = openai.ChatCompletion.create( model="gpt-3.5-turbo", @@ -1148,12 +1231,10 @@ def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kateg consistency = line.split(":", 1)[1].strip() elif line.lower().startswith("begründung:"): justification = line.split(":", 1)[1].strip() - # Prüfe, ob der vorgeschlagene Branchentext exakt im Ziel-Branchenschema enthalten ist if branch.lower() not in [tb.lower() for tb in target_branches]: justification = "Vorgeschlagene Branche entspricht nicht dem Ziel-Branchenschema." branch = "k.A." consistency = "X" - # Falls der Vorschlag exakt mit der in CRM festgelegten Branche übereinstimmt, brauchen wir keine Begründung. if crm_branche.strip() and branch.lower() == crm_branche.strip().lower(): justification = "" consistency = "ok" @@ -1255,7 +1336,9 @@ def main(): MODE = "1" LOG_FILE = create_log_filename(MODE) debug_print(f"Start Betriebsmodus {MODE}") - log_prompt_overview() + # Log-Prompt Übersicht + for entry in prompt_overview()[1:]: + debug_print(f"{entry[0]}: {entry[1]}") dp = DataProcessor() dp.process_rows() print(f"Verarbeitung abgeschlossen. Logfile: {LOG_FILE}")