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 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: <vorgeschlagene Branche>
Übereinstimmung: <ok oder X>
Begründung: <kurze Begründung>
3. Es erfolgt eine Prüfung des ChatGPTVorschlags:
- 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 CRMWert (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 ChatGPTVorschlag 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):