diff --git a/brancheneinstufung.py b/brancheneinstufung.py index c5a8592d..8698bd34 100644 --- a/brancheneinstufung.py +++ b/brancheneinstufung.py @@ -28,6 +28,7 @@ import unicodedata import csv import gender_guesser.detector as gender from urllib.parse import urlparse, urlencode +from difflib import SequenceMatcher # Optional: tiktoken für Token-Zählung (Modus 8) try: @@ -499,14 +500,14 @@ def compare_umsatz_values(crm, wiki): def is_valid_branch(branch): """ - Prüft, ob der gegebene Branchenwert grundsätzlich gültig ist. - Als gültig erachten wir: - - Einen nicht-leeren String, der nicht "k.A." (unabhängig von Groß-/Kleinschreibung) ist. - - Der String sollte mindestens ein hierarchisches Trennzeichen '>' enthalten. + Prüft, ob der übergebene Branch-String grundsätzlich gültig ist. + Gültig ist ein String, der: + - nicht leer und nicht "k.A." (unabhängig von Groß-/Kleinschreibung) ist, + - mindestens ein hierarchisches Trennzeichen ">" enthält. """ if not branch or branch.strip() == "": return False - if branch.lower() == "k.a.": + if branch.strip().lower() == "k.a.": return False if ">" not in branch: return False @@ -514,9 +515,8 @@ def is_valid_branch(branch): def branch_matches_target_schema(branch): """ - Überprüft, ob der übergebene Branchenwert zum Ziel-Branchenschema passt. - Als Heuristik nutzen wir hier die definierten Fokusbranchen – also, ob der branch-Wert - mit einem der erlaubten Präfixe beginnt. Diese Fokusbranchen sind gemäß Alignment Demo: + Überprüft, ob der Branch zum Ziel-Branchenschema passt. + Als Fokus werden die definierten Präfixe verwendet (laut Alignment Demo): - "service provider" - "hersteller / produzenten" - "sonstige" @@ -532,6 +532,32 @@ def branch_matches_target_schema(branch): return True return False +def extract_suffix(branch): + """ + Extrahiert den Teil hinter dem ">" als Suffix. + Falls kein ">" vorhanden ist, wird der gesamte String zurückgegeben. + """ + if ">" in branch: + return branch.split(">")[-1].strip() + return branch.strip() + +def merge_with_prefix(suggestion, crm_branch): + """ + Falls der ChatGPT-Vorschlag kein ">" enthält, wird der Präfix aus dem CRM-Branchenwert übernommen. + Beispiel: CRM: "Hersteller / Produzenten > Automaten (Vending, Slot)" und + Vorschlag: "Automaten (Vending, Slot)" ergeben "Hersteller / Produzenten > Automaten (Vending, Slot)". + """ + if ">" in crm_branch: + prefix = crm_branch.split(">")[0].strip() + return prefix + " > " + suggestion.strip() + return suggestion.strip() + +def fuzzy_similarity(str1, str2): + """ + Gibt den Ähnlichkeitswert (zwischen 0 und 1) der beiden Strings zurück. + """ + return SequenceMatcher(None, str1, str2).ratio() + # ==================== TOKEN COUNT FUNCTION ==================== def token_count(text): if tiktoken: @@ -1443,35 +1469,28 @@ def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kateg Vorgehen: 1. Es wird ein aggregierter Prompt mit folgenden Angaben erstellt: - - CRM-Branche - - Externe Beschreibung (z. B. aus der CRM-Beschreibung) - - Wikipedia-Branche - - Wikipedia-Kategorien - - Website-Zusammenfassung - 2. Der Prompt wird an ChatGPT übergeben, welches im Format antwortet: + - CRM-Branche + - Externe Beschreibung (z. B. aus der CRM-Beschreibung) + - Wikipedia-Branche + - Wikipedia-Kategorien + - Website-Zusammenfassung + 2. Der Prompt wird an ChatGPT übergeben. Erwartetes Antwortformat: Branche: Übereinstimmung: Begründung: - 3. Es erfolgt eine Prüfung des ChatGPT‑Vorschlags: - - Zunächst wird geprüft, ob der vorgeschlagene Brancheneintrag grundsätzlich gültig ist. - - Anschließend wird kontrolliert, ob der Eintrag dem Ziel-Branchenschema (basierend auf den Fokusbranchen) - entspricht. - 4. Falls einer der Checks fehlschlägt, wird der CRM‑Wert (sofern vorhanden und gültig) als Fallback übernommen. - So wird sichergestellt, dass das Feld (z. B. Spalte W) niemals leer oder "k.A." ausgegeben wird. - - Wichtig: - - Keine wesentlichen Funktionsteile (wie z. B. die Fokusbranchen) wurden entfernt – sie sind in der Prüfung enthalten. - - Die Funktion nutzt die bereits im Projekt vorhandene Funktion normalize_string zur Normalisierung. - - Rückgabe: - Ein Dictionary mit den Schlüsseln "branch", "consistency" und "justification". + 3. Nach dem Parsen erfolgt: + a) Falls der ChatGPT‑Vorschlag kein ">" enthält, wird er mit dem Präfix aus crm_branche ergänzt. + b) Anschließend wird mittels Fuzzy Matching die Ähnlichkeit des (extrahierten) Suffix + zwischen dem finalen Vorschlag und dem Suffix des CRM-Werts überprüft. + Liegt die Ähnlichkeit unter 0.75, erfolgt ein Fallback auf den CRM-Wert. + c) Abschließend wird überprüft, ob der Vorschlag den Fokusbranchen entspricht. + 4. Der finale Wert wird zurückgegeben – garantiert als gültiger, dem Branchenschema entsprechender String. """ 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}'" ) - - # Erstelle den Prompt für ChatGPT (Orientierung an der Alignment Demo) + prompt = ( f"Ordne das Unternehmen anhand folgender Angaben exakt einer Branche des Ziel-Branchenschemas zu:\n" f"CRM-Branche: {crm_branche}\n" @@ -1504,8 +1523,8 @@ def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kateg except Exception as e: debug_print("Fehler bei der ChatGPT-Anfrage: " + str(e)) return {"branch": "k.A.", "consistency": "X", "justification": "ChatGPT-Anfrage fehlgeschlagen."} - - # Parsen der ChatGPT-Antwort + + # Parsen der Antwort suggested_branch = "" consistency = "" justification = "" @@ -1518,31 +1537,39 @@ def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kateg justification = line.split(":", 1)[1].strip() debug_print(f"Extrahiert: Branche='{suggested_branch}', Übereinstimmung='{consistency}', Begründung='{justification}'") - - # Normalisiere die Werte (normalize_string muss in deinem Projekt vorhanden sein) - norm_crm = normalize_string(crm_branche) + + # Entferne unerwünschte Präfixe (z.B. "CRM-Branche:") falls vorhanden + if suggested_branch.lower().startswith("crm-branche"): + suggested_branch = suggested_branch.split(":", 1)[-1].strip() + norm_suggested = normalize_string(suggested_branch) + norm_crm = normalize_string(crm_branche) - # Überprüfe, ob der ChatGPT-Vorschlag grundsätzlich gültig ist - if not is_valid_branch(norm_suggested): - debug_print(f"Vorgeschlagene Branche '{suggested_branch}' (normiert: '{norm_suggested}') ist ungültig.") + # Ergänze Hierarchie falls ">" fehlt + if ">" not in norm_suggested: if crm_branche and crm_branche.lower() != "k.a.": - debug_print("Fallback: CRM-Wert verwendet.") - return {"branch": crm_branche, "consistency": "ok", "justification": "Fallback: CRM-Wert verwendet."} - else: - return {"branch": "k.A.", "consistency": "X", "justification": "Kein gültiger Brancheneintrag gefunden."} + merged = merge_with_prefix(norm_suggested, norm_crm) + debug_print(f"Ergänzung der Hierarchie: Zusammenführen von CRM-Präfix mit ChatGPT-Vorschlag. Ergebnis: '{merged}'") + norm_suggested = merged - # Überprüfe, ob der ChatGPT-Vorschlag dem Ziel-Branchenschema entspricht + # Fuzzy Matching: Vergleiche den Suffix (also den Teil nach ">") des finalen Vorschlags mit dem CRM-Suffix + crm_suffix = extract_suffix(norm_crm) + suggestion_suffix = extract_suffix(norm_suggested) + similarity = fuzzy_similarity(suggestion_suffix, crm_suffix) + debug_print(f"Fuzzy Matching: Ähnlichkeit zwischen Suffix '{suggestion_suffix}' und '{crm_suffix}' = {similarity:.2f}") + + # Schwellenwert (z. B. 0.75); falls zu geringe Ähnlichkeit, Fallback auf CRM-Wert + if similarity < 0.75: + debug_print("Fuzzy Matching hat eine zu geringe Übereinstimmung ergeben. Fallback: CRM-Wert verwendet.") + return {"branch": crm_branche, "consistency": "ok", "justification": "Fallback: CRM-Wert verwendet."} + + # Überprüfe, ob der (ggf. hierarchisch ergänzte) Vorschlag den Fokusbranchen entspricht if not branch_matches_target_schema(norm_suggested): - debug_print(f"Vorgeschlagene Branche '{suggested_branch}' (normiert: '{norm_suggested}') entspricht nicht exakt dem Ziel-Branchenschema.") - if crm_branche and crm_branche.lower() != "k.a.": - debug_print("Fallback: CRM-Wert verwendet.") - return {"branch": crm_branche, "consistency": "ok", "justification": "Fallback: CRM-Wert verwendet."} - else: - return {"branch": "k.A.", "consistency": "X", "justification": "Vorgeschlagene Branche entspricht nicht dem Ziel-Branchenschema."} + debug_print(f"Vorgeschlagene Branche '{suggested_branch}' (normiert: '{norm_suggested}') entspricht nicht dem Ziel-Branchenschema. Fallback: CRM-Wert verwendet.") + return {"branch": crm_branche, "consistency": "ok", "justification": "Fallback: CRM-Wert verwendet."} debug_print(f"Endergebnis Branchenbewertung: Branche='{suggested_branch}', Übereinstimmung='{consistency}', Begründung='{justification}'") - return {"branch": suggested_branch, "consistency": consistency, "justification": justification} + return {"branch": norm_suggested, "consistency": consistency, "justification": justification} def evaluate_servicetechnicians_estimate(company_name, company_data):