v1.5.6: Fallback-Mechanismus in evaluate_branche_chatgpt verbessert

- evaluate_branche_chatgpt: Fallback auf CRM-Wert implementiert, wenn ChatGPT-Vorschlag nicht valide ist  
- Helper-Funktionen is_valid_branch und branch_matches_target_schema zur Überprüfung der Branchenwerte hinzugefügt  
- Fokusbranchen (service provider, hersteller / produzenten, sonstige) bleiben erhalten
This commit is contained in:
2025-04-14 14:53:20 +00:00
parent 2c340537ef
commit 2d24533a1b

View File

@@ -1,18 +1,15 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Version: v1.5.5 Version: v1.5.6
Datum: {aktuelles Datum} Datum: {aktuelles Datum}
Git-Überschrift (max. 100 Zeichen): Git-Überschrift (max. 100 Zeichen):
1.5.4: Dispatcher und modulare Batch-Prozesse für Wiki, Website und Branch integriert v1.5.6: Fallback-Mechanismus in evaluate_branche_chatgpt verbessert
Git-Änderungsbeschreibung: Git-Änderungsbeschreibung:
- Neuer run_dispatcher, der den Startpunkt (erste Zeile ohne Zeitstempel in AO ab Zeile 7) - evaluate_branche_chatgpt: Fallback auf CRM-Wert implementiert, wenn ChatGPT-Vorschlag nicht valide ist
ermittelt und den verarbeitenden Bereich (z. B. 50 Zeilen) definiert. - Helper-Funktionen is_valid_branch und branch_matches_target_schema zur Überprüfung der Branchenwerte hinzugefügt
- Separate Batch-Funktionen: process_wiki_batch (Spalten SY), process_website_batch (Spalten AR/AS) - Fokusbranchen (service provider, hersteller / produzenten, sonstige) bleiben erhalten
und process_branch_batch (Spalten WY) werden je nach Modus aufgerufen.
- Erlaubt getrennte oder kombinierte Durchläufe via Modus-Parameter.
- Verbesserte Log-Ausgaben unterstützen die Fehleranalyse.
""" """
@@ -40,7 +37,7 @@ except ImportError:
# ==================== KONFIGURATION ==================== # ==================== KONFIGURATION ====================
class Config: class Config:
VERSION = "v1.5.5" VERSION = "v1.5.6"
LANG = "de" LANG = "de"
CREDENTIALS_FILE = "service_account.json" CREDENTIALS_FILE = "service_account.json"
SHEET_URL = "https://docs.google.com/spreadsheets/d/1u_gHr9JUfmV1-iviRzbSe3575QEp7KLhK5jFV_gJcgo" SHEET_URL = "https://docs.google.com/spreadsheets/d/1u_gHr9JUfmV1-iviRzbSe3575QEp7KLhK5jFV_gJcgo"
@@ -500,6 +497,41 @@ def compare_umsatz_values(crm, wiki):
diff_mio = abs(crm_val - wiki_val) diff_mio = abs(crm_val - wiki_val)
return f"Abweichung: {int(round(diff_mio))} Mio €" return f"Abweichung: {int(round(diff_mio))} Mio €"
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.
"""
if not branch or branch.strip() == "":
return False
if branch.lower() == "k.a.":
return False
if ">" not in branch:
return False
return True
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:
- "service provider"
- "hersteller / produzenten"
- "sonstige"
"""
allowed_prefixes = [
"service provider",
"hersteller / produzenten",
"sonstige"
]
branch_lower = branch.lower()
for prefix in allowed_prefixes:
if branch_lower.startswith(prefix):
return True
return False
# ==================== TOKEN COUNT FUNCTION ==================== # ==================== TOKEN COUNT FUNCTION ====================
def token_count(text): def token_count(text):
if tiktoken: if tiktoken:
@@ -1406,145 +1438,111 @@ class WikipediaScraper:
# ==================== NEUE FUNKTION: Angepasste evaluate_branche_chatgpt ==================== # ==================== NEUE FUNKTION: Angepasste evaluate_branche_chatgpt ====================
def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kategorien, website_summary): def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kategorien, website_summary):
import csv """
from datetime import datetime Ordnet ein Unternehmen exakt einer Branche des Ziel-Branchenschemas zu.
# Hier sollte auch die Funktion debug_print und normalize_company_name verfügbar sein.
# Ich gehe davon aus, dass diese Funktionen bereits definiert sind.
def load_target_branches(): Vorgehen:
try: 1. Es wird ein aggregierter Prompt mit folgenden Angaben erstellt:
with open("ziel_Branchenschema.csv", "r", encoding="utf-8-sig") as csvfile: - CRM-Branche
reader = csv.reader(csvfile) - Externe Beschreibung (z. B. aus der CRM-Beschreibung)
# Spalte 0 wird getrimmt und nur nicht-leere Zeilen übernommen - Wikipedia-Branche
branches = [row[0].strip() for row in reader if row and row[0].strip()] - Wikipedia-Kategorien
return branches - Website-Zusammenfassung
except Exception as e: 2. Der Prompt wird an ChatGPT übergeben, welches im Format antwortet:
debug_print(f"Fehler beim Laden des Ziel-Branchenschemas: {e}") Branche: <vorgeschlagene Branche>
return [] Ü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.
# Lade das Ziel-Branchenschema und normalisiere die Werte Wichtig:
target_branches = load_target_branches() - Keine wesentlichen Funktionsteile (wie z.B. die Fokusbranchen) wurden entfernt sie sind in der Prüfung enthalten.
norm_targets = [normalize_company_name(tb) for tb in target_branches] - Die Funktion nutzt die bereits im Projekt vorhandene Funktion normalize_string zur Normalisierung.
# Definiere die Fokus-Branchen, die im Prompt berücksichtigt werden sollen Rückgabe:
focus_branches = [ Ein Dictionary mit den Schlüsseln "branch", "consistency" und "justification".
"Gutachter / Versicherungen > Baugutachter", """
"Gutachter / Versicherungen > Technische Gutachten", debug_print(
"Gutachter / Versicherungen > Versicherungsgutachten", f"Verwendete Angaben: CRM-Branche='{crm_branche}', externe Beschreibung='{beschreibung}', "
"Gutachter / Versicherungen > Medizinische Gutachten", f"Wiki-Branche='{wiki_branche}', Wiki-Kategorien='{wiki_kategorien}', Website-Zusammenfassung='{website_summary}'"
"Hersteller / Produzenten > Anlagenbau", )
"Hersteller / Produzenten > Automaten (Vending, Slot)",
"Hersteller / Produzenten > Gebäudetechnik Allgemein", # Erstelle den Prompt für ChatGPT (Orientierung an der Alignment Demo)
"Hersteller / Produzenten > Gebäudetechnik Heizung, Lüftung, Klima", prompt = (
"Hersteller / Produzenten > Maschinenbau", f"Ordne das Unternehmen anhand folgender Angaben exakt einer Branche des Ziel-Branchenschemas zu:\n"
"Hersteller / Produzenten > Medizintechnik", f"CRM-Branche: {crm_branche}\n"
"Service provider (Dienstleister) > Aufzüge und Rolltreppen", f"Beschreibung: {beschreibung}\n"
"Service provider (Dienstleister) > Feuer- und Sicherheitssysteme", f"Wikipedia-Branche: {wiki_branche}\n"
"Service provider (Dienstleister) > Servicedienstleister / Reparatur ohne Produktion", f"Wikipedia-Kategorien: {wiki_kategorien}\n"
"Service provider (Dienstleister) > Facility Management", f"Website-Zusammenfassung: {website_summary}\n\n"
"Versorger > Telekommunikation" "Antworte im Format:\n"
] "Branche: <vorgeschlagene Branche>\n"
focus_branches_str = "\n".join(focus_branches) "Übereinstimmung: <ok oder X>\n"
"Begründung: <kurze Begründung>"
)
# API-Key laden falls nicht verfügbar, gibt die Funktion einen Fallback zurück
try: try:
with open("api_key.txt", "r") as f: with open("api_key.txt", "r") as f:
api_key = f.read().strip() api_key = f.read().strip()
except Exception as e: except Exception as e:
debug_print(f"Fehler beim Lesen des API-Tokens (Branche): {e}") debug_print("Fehler beim Lesen des API-Tokens: " + str(e))
return {"branch": "k.A.", "consistency": "k.A.", "justification": "k.A."} return {"branch": "k.A.", "consistency": "X", "justification": "Kein API-Key gefunden."}
openai.api_key = api_key openai.api_key = api_key
# Falls kein Wikipedia-Artikel vorhanden ist, verwende die Website-Zusammenfassung als Fallback.
if wiki_branche.strip().lower() == "k.a.":
debug_print("Kein Wikipedia-Artikel vorhanden verwende Website-Zusammenfassung als Branchenbeschreibung-Fallback.")
used_description = website_summary
else:
used_description = beschreibung
debug_print(f"Verwendete Angaben: CRM-Branche='{crm_branche}', externe Beschreibung='{beschreibung}', Wiki-Branche='{wiki_branche}', Wiki-Kategorien='{wiki_kategorien}', Website-Zusammenfassung='{website_summary}'")
# Erstelle den System-Prompt, der die Fokus-Branchen als bevorzugte Auswahlmöglichkeiten integriert.
system_prompt = (
"Du bist ein Experte im Field Service Management. Ordne das folgende Unternehmen exakt einer Branche zu.\n\n"
f"CRM-Branche (Spalte F): {crm_branche if crm_branche.strip() != '' else 'k.A.'}\n"
f"Branchenbeschreibung (Spalte G): {used_description if used_description.strip() != '' else 'k.A.'}\n"
f"Wikipedia-Branche (Spalte N): {wiki_branche}\n"
f"Wikipedia-Kategorien (Spalte Q): {wiki_kategorien}\n\n"
"Falls das Unternehmen mehreren Branchen zugeordnet werden könnte, wähle bitte bevorzugt eine aus der folgenden Fokusliste:\n"
f"{focus_branches_str}\n\n"
"Gewichtung:\n"
"1. Wikipedia-Daten (falls vorhanden)\n"
"2. Externe Branchenbeschreibung (Website-Zusammenfassung, falls Wikipedia nicht vorhanden ist)\n"
"3. CRM-Branche\n\n"
"Die Antwort muss exakt einem der Zielbranchen entsprechen. Bitte antworte im Format:\n"
"Branche: <vorgeschlagene Branche>\nÜbereinstimmung: <ok oder X>\nBegründung: <kurze Begründung, falls abweichend, ansonsten leer>"
)
try: try:
response = openai.ChatCompletion.create( response = openai.ChatCompletion.create(
model=Config.TOKEN_MODEL, model=Config.TOKEN_MODEL,
messages=[{"role": "system", "content": system_prompt}], messages=[{"role": "user", "content": prompt}],
temperature=0.0 temperature=0.0
) )
result = response.choices[0].message.content.strip() chat_output = response.choices[0].message.content.strip()
debug_print(f"Branchenabgleich ChatGPT Antwort: '{result}'") debug_print(f"Branchenabgleich ChatGPT Antwort: '{chat_output}'")
except Exception as e: except Exception as e:
debug_print(f"Fehler beim Aufruf der ChatGPT API für Branchenabgleich: {e}") debug_print("Fehler bei der ChatGPT-Anfrage: " + str(e))
# Fallback: Wenn keine ChatGPT-Einschätzung möglich ist return {"branch": "k.A.", "consistency": "X", "justification": "ChatGPT-Anfrage fehlgeschlagen."}
if normalize_company_name(crm_branche) in norm_targets:
debug_print("Fallback: Verwende CRM-Branche, da keine ChatGPT-Einschätzung verfügbar ist.")
return {"branch": crm_branche, "consistency": "ok", "justification": "Keine ChatGPT-Einschätzung; CRM-Wert verwendet."}
else:
return {"branch": "k.A.", "consistency": "X", "justification": "Keine ChatGPT-Einschätzung möglich und CRM-Wert ungültig."}
# Parse die Antwort von ChatGPT # Parsen der ChatGPT-Antwort
chat_branch = None suggested_branch = ""
chat_consistency = None consistency = ""
chat_justification = "" justification = ""
for line in result.split("\n"): for line in chat_output.split("\n"):
lower_line = line.lower() if line.startswith("Branche:"):
if lower_line.startswith("branche:"): suggested_branch = line.split(":", 1)[1].strip()
chat_branch = line.split(":", 1)[1].strip() elif line.startswith("Übereinstimmung:"):
elif lower_line.startswith("übereinstimmung:"): consistency = line.split(":", 1)[1].strip()
chat_consistency = line.split(":", 1)[1].strip() elif line.startswith("Begründung:"):
elif lower_line.startswith("begründung:"): justification = line.split(":", 1)[1].strip()
chat_justification = line.split(":", 1)[1].strip()
debug_print(f"Extrahiert: Branche='{chat_branch}', Übereinstimmung='{chat_consistency}', Begründung='{chat_justification}'") debug_print(f"Extrahiert: Branche='{suggested_branch}', Übereinstimmung='{consistency}', Begründung='{justification}'")
# Normiere die Werte zum Vergleich # Normalisiere die Werte (normalize_string muss in deinem Projekt vorhanden sein)
norm_crm = normalize_company_name(crm_branche) norm_crm = normalize_string(crm_branche)
norm_chat = normalize_company_name(chat_branch) if chat_branch else "" norm_suggested = normalize_string(suggested_branch)
debug_print(f"Normierte Werte: CRM='{norm_crm}', ChatGPT-Vorschlag='{norm_chat}'")
# Falls ChatGPT keine aussagekräftige Antwort liefert, verwende den CRM-Wert als Fallback # Überprüfe, ob der ChatGPT-Vorschlag grundsätzlich gültig ist
if not norm_chat: if not is_valid_branch(norm_suggested):
debug_print("Keine aussagekräftige ChatGPT-Antwort erhalten, fallback: CRM-Branche.") debug_print(f"Vorgeschlagene Branche '{suggested_branch}' (normiert: '{norm_suggested}') ist ungültig.")
if norm_crm in norm_targets: if crm_branche and crm_branche.lower() != "k.a.":
return {"branch": crm_branche, "consistency": "ok", "justification": "Keine ChatGPT-Antwort; CRM-Wert übernommen."} debug_print("Fallback: CRM-Wert verwendet.")
else:
return {"branch": "k.A.", "consistency": "X", "justification": "Keine ChatGPT-Antwort und CRM-Wert passt nicht."}
# Überprüfe, ob der ChatGPT-Vorschlag exakt im Ziel-Branchenschema enthalten ist
if norm_chat not in norm_targets:
debug_print(f"Vorgeschlagene Branche '{chat_branch}' (normiert: '{norm_chat}') entspricht nicht exakt dem Ziel-Branchenschema.")
# Fallback: Falls der CRM-Wert gültig ist, verwende ihn.
if norm_crm in norm_targets:
debug_print("Fallback: CRM-Branche entspricht dem Ziel-Branchenschema, daher wird sie übernommen.")
return {"branch": crm_branche, "consistency": "ok", "justification": "Fallback: CRM-Wert verwendet."} return {"branch": crm_branche, "consistency": "ok", "justification": "Fallback: CRM-Wert verwendet."}
else: else:
debug_print("Fallback: Keine gültige Branche gefunden.") return {"branch": "k.A.", "consistency": "X", "justification": "Kein gültiger Brancheneintrag gefunden."}
return {"branch": "k.A.", "consistency": "X", "justification": "Vorgeschlagene Branche entspricht nicht dem Ziel-Branchenschema."}
else:
# Vergleiche CRM- und ChatGPT-Vorschlag wenn diese exakt übereinstimmen, ist die Konsistenz "ok"
if norm_crm and norm_crm == norm_chat:
chat_consistency = "ok"
chat_justification = ""
else:
chat_consistency = "X"
debug_print(f"Endergebnis Branchenbewertung: Branche='{chat_branch}', Übereinstimmung='{chat_consistency}', Begründung='{chat_justification}'")
return {"branch": chat_branch, "consistency": chat_consistency, "justification": chat_justification}
# Überprüfe, ob der ChatGPT-Vorschlag dem Ziel-Branchenschema 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"Endergebnis Branchenbewertung: Branche='{suggested_branch}', Übereinstimmung='{consistency}', Begründung='{justification}'")
return {"branch": suggested_branch, "consistency": consistency, "justification": justification}
def evaluate_servicetechnicians_estimate(company_name, company_data): def evaluate_servicetechnicians_estimate(company_name, company_data):