v1.5.7: Fuzzy Matching und Hierarchie-Ergänzung in evaluate_branche_chatgpt

- evaluate_branche_chatgpt: Neuer Schritt zum Ergänzen fehlender Hierarchie mit CRM-Präfix  
- Fuzzy Matching zwischen zusammengesetztem Vorschlag und CRM-Wert eingebaut  
- Sicherstellung, dass der finale Brancheneintrag stets dem Ziel-Branchenschema entspricht
This commit is contained in:
2025-04-14 15:41:26 +00:00
parent b0f7af9582
commit ffe8cf39bf

View File

@@ -28,6 +28,7 @@ import unicodedata
import csv import csv
import gender_guesser.detector as gender import gender_guesser.detector as gender
from urllib.parse import urlparse, urlencode from urllib.parse import urlparse, urlencode
from difflib import SequenceMatcher
# Optional: tiktoken für Token-Zählung (Modus 8) # Optional: tiktoken für Token-Zählung (Modus 8)
try: try:
@@ -499,14 +500,14 @@ def compare_umsatz_values(crm, wiki):
def is_valid_branch(branch): def is_valid_branch(branch):
""" """
Prüft, ob der gegebene Branchenwert grundsätzlich gültig ist. Prüft, ob der übergebene Branch-String grundsätzlich gültig ist.
Als gültig erachten wir: Gültig ist ein String, der:
- Einen nicht-leeren String, der nicht "k.A." (unabhängig von Groß-/Kleinschreibung) ist. - nicht leer und nicht "k.A." (unabhängig von Groß-/Kleinschreibung) ist,
- Der String sollte mindestens ein hierarchisches Trennzeichen '>' enthalten. - mindestens ein hierarchisches Trennzeichen ">" enthält.
""" """
if not branch or branch.strip() == "": if not branch or branch.strip() == "":
return False return False
if branch.lower() == "k.a.": if branch.strip().lower() == "k.a.":
return False return False
if ">" not in branch: if ">" not in branch:
return False return False
@@ -514,9 +515,8 @@ def is_valid_branch(branch):
def branch_matches_target_schema(branch): def branch_matches_target_schema(branch):
""" """
Überprüft, ob der übergebene Branchenwert zum Ziel-Branchenschema passt. Überprüft, ob der Branch zum Ziel-Branchenschema passt.
Als Heuristik nutzen wir hier die definierten Fokusbranchen also, ob der branch-Wert Als Fokus werden die definierten Präfixe verwendet (laut Alignment Demo):
mit einem der erlaubten Präfixe beginnt. Diese Fokusbranchen sind gemäß Alignment Demo:
- "service provider" - "service provider"
- "hersteller / produzenten" - "hersteller / produzenten"
- "sonstige" - "sonstige"
@@ -532,6 +532,32 @@ def branch_matches_target_schema(branch):
return True return True
return False 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 ==================== # ==================== TOKEN COUNT FUNCTION ====================
def token_count(text): def token_count(text):
if tiktoken: if tiktoken:
@@ -1443,35 +1469,28 @@ def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kateg
Vorgehen: Vorgehen:
1. Es wird ein aggregierter Prompt mit folgenden Angaben erstellt: 1. Es wird ein aggregierter Prompt mit folgenden Angaben erstellt:
- CRM-Branche - CRM-Branche
- Externe Beschreibung (z. B. aus der CRM-Beschreibung) - Externe Beschreibung (z. B. aus der CRM-Beschreibung)
- Wikipedia-Branche - Wikipedia-Branche
- Wikipedia-Kategorien - Wikipedia-Kategorien
- Website-Zusammenfassung - Website-Zusammenfassung
2. Der Prompt wird an ChatGPT übergeben, welches im Format antwortet: 2. Der Prompt wird an ChatGPT übergeben. Erwartetes Antwortformat:
Branche: <vorgeschlagene Branche> Branche: <vorgeschlagene Branche>
Übereinstimmung: <ok oder X> Übereinstimmung: <ok oder X>
Begründung: <kurze Begründung> Begründung: <kurze Begründung>
3. Es erfolgt eine Prüfung des ChatGPTVorschlags: 3. Nach dem Parsen erfolgt:
- Zunächst wird geprüft, ob der vorgeschlagene Brancheneintrag grundsätzlich gültig ist. a) Falls der ChatGPTVorschlag kein ">" enthält, wird er mit dem Präfix aus crm_branche ergänzt.
- Anschließend wird kontrolliert, ob der Eintrag dem Ziel-Branchenschema (basierend auf den Fokusbranchen) b) Anschließend wird mittels Fuzzy Matching die Ähnlichkeit des (extrahierten) Suffix
entspricht. zwischen dem finalen Vorschlag und dem Suffix des CRM-Werts überprüft.
4. Falls einer der Checks fehlschlägt, wird der CRMWert (sofern vorhanden und gültig) als Fallback übernommen. Liegt die Ähnlichkeit unter 0.75, erfolgt ein Fallback auf den CRM-Wert.
So wird sichergestellt, dass das Feld (z.B. Spalte W) niemals leer oder "k.A." ausgegeben wird. 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.
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".
""" """
debug_print( debug_print(
f"Verwendete Angaben: CRM-Branche='{crm_branche}', externe Beschreibung='{beschreibung}', " f"Verwendete Angaben: CRM-Branche='{crm_branche}', externe Beschreibung='{beschreibung}', "
f"Wiki-Branche='{wiki_branche}', Wiki-Kategorien='{wiki_kategorien}', Website-Zusammenfassung='{website_summary}'" 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 = ( prompt = (
f"Ordne das Unternehmen anhand folgender Angaben exakt einer Branche des Ziel-Branchenschemas zu:\n" f"Ordne das Unternehmen anhand folgender Angaben exakt einer Branche des Ziel-Branchenschemas zu:\n"
f"CRM-Branche: {crm_branche}\n" f"CRM-Branche: {crm_branche}\n"
@@ -1505,7 +1524,7 @@ def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kateg
debug_print("Fehler bei der ChatGPT-Anfrage: " + str(e)) debug_print("Fehler bei der ChatGPT-Anfrage: " + str(e))
return {"branch": "k.A.", "consistency": "X", "justification": "ChatGPT-Anfrage fehlgeschlagen."} return {"branch": "k.A.", "consistency": "X", "justification": "ChatGPT-Anfrage fehlgeschlagen."}
# Parsen der ChatGPT-Antwort # Parsen der Antwort
suggested_branch = "" suggested_branch = ""
consistency = "" consistency = ""
justification = "" justification = ""
@@ -1519,30 +1538,38 @@ def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kateg
debug_print(f"Extrahiert: Branche='{suggested_branch}', Übereinstimmung='{consistency}', Begründung='{justification}'") debug_print(f"Extrahiert: Branche='{suggested_branch}', Übereinstimmung='{consistency}', Begründung='{justification}'")
# Normalisiere die Werte (normalize_string muss in deinem Projekt vorhanden sein) # Entferne unerwünschte Präfixe (z.B. "CRM-Branche:") falls vorhanden
norm_crm = normalize_string(crm_branche) if suggested_branch.lower().startswith("crm-branche"):
suggested_branch = suggested_branch.split(":", 1)[-1].strip()
norm_suggested = normalize_string(suggested_branch) norm_suggested = normalize_string(suggested_branch)
norm_crm = normalize_string(crm_branche)
# Überprüfe, ob der ChatGPT-Vorschlag grundsätzlich gültig ist # Ergänze Hierarchie falls ">" fehlt
if not is_valid_branch(norm_suggested): if ">" not in norm_suggested:
debug_print(f"Vorgeschlagene Branche '{suggested_branch}' (normiert: '{norm_suggested}') ist ungültig.")
if crm_branche and crm_branche.lower() != "k.a.": if crm_branche and crm_branche.lower() != "k.a.":
debug_print("Fallback: CRM-Wert verwendet.") merged = merge_with_prefix(norm_suggested, norm_crm)
return {"branch": crm_branche, "consistency": "ok", "justification": "Fallback: CRM-Wert verwendet."} debug_print(f"Ergänzung der Hierarchie: Zusammenführen von CRM-Präfix mit ChatGPT-Vorschlag. Ergebnis: '{merged}'")
else: norm_suggested = merged
return {"branch": "k.A.", "consistency": "X", "justification": "Kein gültiger Brancheneintrag gefunden."}
# Ü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): if not branch_matches_target_schema(norm_suggested):
debug_print(f"Vorgeschlagene Branche '{suggested_branch}' (normiert: '{norm_suggested}') entspricht nicht exakt dem Ziel-Branchenschema.") debug_print(f"Vorgeschlagene Branche '{suggested_branch}' (normiert: '{norm_suggested}') entspricht nicht dem Ziel-Branchenschema. Fallback: CRM-Wert verwendet.")
if crm_branche and crm_branche.lower() != "k.a.": return {"branch": crm_branche, "consistency": "ok", "justification": "Fallback: CRM-Wert verwendet."}
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"Endergebnis Branchenbewertung: Branche='{suggested_branch}', Übereinstimmung='{consistency}', Begründung='{justification}'") 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): def evaluate_servicetechnicians_estimate(company_name, company_data):