From 7c6bb5abbfd2fda3c5fccada69ce33e13d68dae5 Mon Sep 17 00:00:00 2001 From: Floke Date: Tue, 15 Apr 2025 11:27:53 +0000 Subject: [PATCH] bugfix --- brancheneinstufung.py | 178 +++++++++++++++++++++++------------------- 1 file changed, 98 insertions(+), 80 deletions(-) diff --git a/brancheneinstufung.py b/brancheneinstufung.py index a01004a4..323d052e 100644 --- a/brancheneinstufung.py +++ b/brancheneinstufung.py @@ -389,38 +389,43 @@ def is_valid_company_article(wiki_categories): return True return False -def load_target_schema_string(csv_filepath="ziel_Branchenschema.csv"): +def load_target_schema(csv_filepath="ziel_Branchenschema.csv"): """ Liest das Ziel-Branchenschema aus der CSV-Datei ein. Die CSV-Datei sollte in Spalte A den externen (Wikipedia-)Branchenbegriff und in Spalte B den zugehörigen Zielwert enthalten. - Es werden alle eindeutigen Zielwerte gesammelt und als Aufzählung zurückgegeben. + + Returns: + mapping (dict): Ein Dictionary, das externe Branchenbegriffe (lowercase) auf + die zugehörigen Zielwerte (lowercase) abbildet. + schema_string (str): Eine formattierte Zeichenkette als Aufzählung der gültigen + Zielbranchen, die im Prompt übergeben werden kann. + allowed_targets (list): Eine sortierte Liste der eindeutigen Zielwerte. """ import csv + mapping = {} valid_targets = set() try: with open(csv_filepath, encoding="utf-8") as f: - reader = csv.reader(f) + reader = csv.reader(f, delimiter=";") for row in reader: - # Wir erwarten mindestens zwei Spalten: Externer Begriff und Zielwert if len(row) >= 2: - target = row[1].strip() + external = row[0].strip().lower() + target = row[1].strip().lower() if target: + mapping[external] = target valid_targets.add(target) except Exception as e: debug_print("Fehler beim Einlesen des Ziel-Branchenschemas: " + str(e)) - return "Ziel-Branchenschema nicht verfügbar." + return {}, "Ziel-Branchenschema nicht verfügbar.", [] - if valid_targets: - # Wir können die Zielwerte sortieren, um immer dieselbe Reihenfolge zu gewährleisten. - sorted_targets = sorted(valid_targets, key=lambda s: s.lower()) - return ( - "Ziel-Branchenschema: Folgende Branchenbereiche sind gültig:\n" + - "\n".join(f"- {value}" for value in sorted_targets) + - "\nBitte ordne das Unternehmen ausschließlich in einen dieser Bereiche ein." - ) - else: - return "Ziel-Branchenschema nicht verfügbar." + sorted_targets = sorted(valid_targets, key=lambda s: s.lower()) + schema_string = ( + "Ziel-Branchenschema: Folgende Branchenbereiche sind gültig:\n" + + "\n".join(f"- {value}" for value in sorted_targets) + + "\nBitte ordne das Unternehmen ausschließlich in einen dieser Bereiche ein." + ) + return mapping, schema_string, sorted_targets def serp_website_lookup(company_name): """ @@ -1533,47 +1538,55 @@ class WikipediaScraper: # ==================== NEUE FUNKTION: Angepasste evaluate_branche_chatgpt ==================== def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kategorien, website_summary): """ - Ordnet das Unternehmen anhand externer Quellen und interner Daten exakt einer Branche - des Ziel-Branchenschemas zu. + Ordnet das Unternehmen basierend auf den angegebenen Informationen exakt einer Branche + des in der CSV-Datei hinterlegten Ziel-Branchenschemas zu. - Vorgehen: - 1. Es wird ein aggregierter Prompt mit den Angaben (CRM-Branche, Beschreibung, Wikipedia-Branche, - Wikipedia-Kategorien, Website-Zusammenfassung) erstellt und an ChatGPT geschickt. - 2. Zusätzlich wird das gültige Ziel-Branchenschema (als Übersicht) dem Prompt angehängt. - 3. Der von ChatGPT zurückgegebene externe Branchenvorschlag wird geparst, normalisiert und - mittels map_external_branch() in den standardisierten Zielwert überführt. - 4. Falls erforderlich, wird der CRM-Präfix ergänzt. - 5. Falls kein sinnvoller externer Vorschlag vorliegt, erfolgt der Fallback auf den CRM-Wert. + Der System-Prompt enthält nun den erlaubten Branchenbereich, und der von ChatGPT gegebene Vorschlag + wird bereinigt und gegen die Einträge des Ziel-Schemas validiert. + + Falls der Vorschlag nicht validiert werden kann, erfolgt ein Fallback auf den CRM-Wert. + + Args: + crm_branche (str): Branche laut CRM + beschreibung (str): Unternehmensbeschreibung (CRM) + wiki_branche (str): Branche aus Wikipedia (falls vorhanden) + wiki_kategorien (str): Wikipedia-Kategorien + website_summary (str): Zusammenfassung des Website-Inhalts + + Returns: + dict: Enthält "branch", "consistency" (ok oder X) und "justification". """ - debug_print( - f"Verwendete Angaben: CRM-Branche='{crm_branche}', externe Beschreibung='{beschreibung}', " - f"Wiki-Branche='{wiki_branche}', Wiki-Kategorien='{wiki_kategorien}', Website-Zusammenfassung='{website_summary}'" - ) - - # Hier definieren wir das Ziel-Branchenschema als Text, der ChatGPT als Grundlage dienen soll. - target_schema_info = load_target_schema_string() - - prompt = ( - f"Ordne das Unternehmen anhand folgender Angaben exakt einer Branche des Ziel-Branchenschemas zu:\n" - f"CRM-Branche: {crm_branche}\n" - f"Beschreibung: {beschreibung}\n" - f"Wikipedia-Branche: {wiki_branche}\n" - f"Wikipedia-Kategorien: {wiki_kategorien}\n" - f"Website-Zusammenfassung: {website_summary}\n\n" - f"{target_schema_info}\n\n" - "Antworte im Format:\n" - "Branche: \n" - "Übereinstimmung: \n" - "Begründung: " - ) + # Lade Mapping und Liste der erlaubten Ziel-Branchen + mapping, allowed_branches = load_target_branches() + + # Baue den Text für das Ziel-Branchenschema, der im System-Prompt an ChatGPT übergeben wird + schema_lines = ["Ziel-Branchenschema: Folgende Branchenbereiche sind gültig:"] + for branch in allowed_branches: + schema_lines.append(f"- {branch}") + target_schema_info = "\n".join(schema_lines) + # Erstelle den System-Prompt inklusive der Zielvorgaben +target_mapping, target_schema_string, allowed_targets = load_target_schema() + +prompt = ( + f"{target_schema_string}\n\n" + f"Ordne das Unternehmen anhand folgender Angaben exakt einer Branche des Ziel-Branchenschemas zu:\n" + f"CRM-Branche: {crm_branche}\n" + f"Beschreibung: {beschreibung}\n" + f"Wikipedia-Branche: {wiki_branche}\n" + f"Wikipedia-Kategorien: {wiki_kategorien}\n" + f"Website-Zusammenfassung: {website_summary}\n\n" + "Antworte im Format:\n" + "Branche: \n" + "Übereinstimmung: \n" + "Begründung: " +) try: with open("api_key.txt", "r") as f: api_key = f.read().strip() except Exception as e: - debug_print("Fehler beim Lesen des API-Tokens: " + str(e)) - return {"branch": "k.A.", "consistency": "X", "justification": "Kein API-Key gefunden."} - + debug_print(f"Fehler beim Lesen des API-Tokens für Brancheneinschätzung: {e}") + return {"branch": crm_branche, "consistency": "X", "justification": "API-Key Fehler"} openai.api_key = api_key try: response = openai.ChatCompletion.create( @@ -1581,44 +1594,49 @@ def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kateg messages=[{"role": "user", "content": prompt}], temperature=0.0 ) - chat_output = response.choices[0].message.content.strip() - debug_print(f"Branchenabgleich ChatGPT Antwort: '{chat_output}'") + chat_response = response.choices[0].message.content.strip() except Exception as e: - debug_print("Fehler bei der ChatGPT-Anfrage: " + str(e)) - return {"branch": "k.A.", "consistency": "X", "justification": "ChatGPT-Anfrage fehlgeschlagen."} + debug_print(f"Fehler bei der ChatGPT-Anfrage für Brancheneinschätzung: {e}") + return {"branch": crm_branche, "consistency": "X", "justification": "API-Anfrage Fehler"} - # Parsen der Antwort von ChatGPT - suggested_branch = "" + # Erwarte ein Format: + # Branche: + # Übereinstimmung: + # Begründung: + lines = chat_response.split("\n") + suggestion = "" consistency = "" - justification = "" - for line in chat_output.split("\n"): - if line.startswith("Branche:"): - suggested_branch = line.split(":", 1)[1].strip() - elif line.startswith("Übereinstimmung:"): + explanation = "" + for line in lines: + if line.lower().startswith("branche:"): + suggestion = line.split(":", 1)[1].strip() + elif line.lower().startswith("übereinstimmung:"): consistency = line.split(":", 1)[1].strip() - elif line.startswith("Begründung:"): - justification = line.split(":", 1)[1].strip() + elif line.lower().startswith("begründung:"): + explanation = line.split(":", 1)[1].strip() - debug_print(f"Ursprünglicher ChatGPT-Vorschlag: '{suggested_branch}', Übereinstimmung='{consistency}', Begründung='{justification}'") + # Bereinige den Vorschlag: entferne unnötige Satzzeichen und konvertiere in Kleinbuchstaben + clean_suggestion = re.sub(r'[^\w\s/&-]', '', suggestion).strip().lower() - # Entferne ggf. den Zusatz "CRM-Branche:" falls vorhanden - if suggested_branch.lower().startswith("crm-branche"): - suggested_branch = suggested_branch.split(":", 1)[-1].strip() + # Falls der bereinigte Vorschlag kein Hierarchie-Trennzeichen ">" enthält, übernehme den Präfix aus der CRM-Branche + if ">" not in clean_suggestion and ">" in crm_branche: + prefix = crm_branche.split(">")[0].strip().lower() + clean_suggestion = prefix + " > " + clean_suggestion - # Mapping anwenden: Externen Vorschlag in den standardisierten Zielwert überführen - mapped_branch = map_external_branch(suggested_branch) - debug_print(f"Nach Mapping erhalten: '{mapped_branch}'") + # Prüfe, ob der bereinigte Vorschlag mit einem erlaubten Eintrag (Fuzzy Matching) übereinstimmt + valid = False + for allowed in allowed_branches: + sim = fuzzy_similarity(clean_suggestion, allowed) + if sim > 0.95: # sehr hoher Ähnlichkeit (kann angepasst werden) + valid = True + # Setze den Vorschlag exakt auf den Zielwert + clean_suggestion = allowed + break + if not valid: + debug_print(f"Mapping ungültig für Vorschlag: '{clean_suggestion}'. Fallback: CRM-Branche ('{crm_branche}') verwendet.") + return {"branch": crm_branche, "consistency": consistency, "justification": "Fallback: CRM-Wert verwendet aufgrund ungültiger ChatGPT-Zuweisung."} - # Hierarchie ergänzen: Falls das Mapping keinen hierarchischen Trennstrich enthält und ein CRM-Präfix vorhanden ist - if ">" not in mapped_branch and crm_branche.lower() != "k.a.": - prefix = crm_branche.split(">")[0].strip() if ">" in crm_branche else "" - if prefix: - mapped_branch = prefix + " > " + mapped_branch - debug_print(f"Ergänzung der Hierarchie: Ergebnis: '{mapped_branch}'") - - final_branch = mapped_branch - debug_print(f"Endergebnis Branchenbewertung: Branche='{final_branch}', Übereinstimmung='{consistency}', Begründung='{justification}'") - return {"branch": final_branch, "consistency": consistency, "justification": justification} + return {"branch": clean_suggestion, "consistency": consistency, "justification": explanation} def evaluate_servicetechnicians_estimate(company_name, company_data):