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
"""
Version: v1.5.5
Version: v1.5.6
Datum: {aktuelles Datum}
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:
- Neuer run_dispatcher, der den Startpunkt (erste Zeile ohne Zeitstempel in AO ab Zeile 7)
ermittelt und den verarbeitenden Bereich (z. B. 50 Zeilen) definiert.
- Separate Batch-Funktionen: process_wiki_batch (Spalten SY), process_website_batch (Spalten AR/AS)
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.
- 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
"""
@@ -40,7 +37,7 @@ except ImportError:
# ==================== KONFIGURATION ====================
class Config:
VERSION = "v1.5.5"
VERSION = "v1.5.6"
LANG = "de"
CREDENTIALS_FILE = "service_account.json"
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)
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 ====================
def token_count(text):
if tiktoken:
@@ -1406,145 +1438,111 @@ class WikipediaScraper:
# ==================== NEUE FUNKTION: Angepasste evaluate_branche_chatgpt ====================
def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kategorien, website_summary):
import csv
from datetime import datetime
# Hier sollte auch die Funktion debug_print und normalize_company_name verfügbar sein.
# Ich gehe davon aus, dass diese Funktionen bereits definiert sind.
"""
Ordnet ein Unternehmen exakt einer Branche des Ziel-Branchenschemas zu.
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:
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".
"""
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}'"
)
def load_target_branches():
try:
with open("ziel_Branchenschema.csv", "r", encoding="utf-8-sig") as csvfile:
reader = csv.reader(csvfile)
# Spalte 0 wird getrimmt und nur nicht-leere Zeilen übernommen
branches = [row[0].strip() for row in reader if row and row[0].strip()]
return branches
except Exception as e:
debug_print(f"Fehler beim Laden des Ziel-Branchenschemas: {e}")
return []
# Lade das Ziel-Branchenschema und normalisiere die Werte
target_branches = load_target_branches()
norm_targets = [normalize_company_name(tb) for tb in target_branches]
# Definiere die Fokus-Branchen, die im Prompt berücksichtigt werden sollen
focus_branches = [
"Gutachter / Versicherungen > Baugutachter",
"Gutachter / Versicherungen > Technische Gutachten",
"Gutachter / Versicherungen > Versicherungsgutachten",
"Gutachter / Versicherungen > Medizinische Gutachten",
"Hersteller / Produzenten > Anlagenbau",
"Hersteller / Produzenten > Automaten (Vending, Slot)",
"Hersteller / Produzenten > Gebäudetechnik Allgemein",
"Hersteller / Produzenten > Gebäudetechnik Heizung, Lüftung, Klima",
"Hersteller / Produzenten > Maschinenbau",
"Hersteller / Produzenten > Medizintechnik",
"Service provider (Dienstleister) > Aufzüge und Rolltreppen",
"Service provider (Dienstleister) > Feuer- und Sicherheitssysteme",
"Service provider (Dienstleister) > Servicedienstleister / Reparatur ohne Produktion",
"Service provider (Dienstleister) > Facility Management",
"Versorger > Telekommunikation"
]
focus_branches_str = "\n".join(focus_branches)
# API-Key laden falls nicht verfügbar, gibt die Funktion einen Fallback zurück
# 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"
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: <vorgeschlagene Branche>\n"
"Übereinstimmung: <ok oder X>\n"
"Begründung: <kurze Begründung>"
)
try:
with open("api_key.txt", "r") as f:
api_key = f.read().strip()
except Exception as e:
debug_print(f"Fehler beim Lesen des API-Tokens (Branche): {e}")
return {"branch": "k.A.", "consistency": "k.A.", "justification": "k.A."}
debug_print("Fehler beim Lesen des API-Tokens: " + str(e))
return {"branch": "k.A.", "consistency": "X", "justification": "Kein API-Key gefunden."}
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:
response = openai.ChatCompletion.create(
model=Config.TOKEN_MODEL,
messages=[{"role": "system", "content": system_prompt}],
messages=[{"role": "user", "content": prompt}],
temperature=0.0
)
result = response.choices[0].message.content.strip()
debug_print(f"Branchenabgleich ChatGPT Antwort: '{result}'")
chat_output = response.choices[0].message.content.strip()
debug_print(f"Branchenabgleich ChatGPT Antwort: '{chat_output}'")
except Exception as e:
debug_print(f"Fehler beim Aufruf der ChatGPT API für Branchenabgleich: {e}")
# Fallback: Wenn keine ChatGPT-Einschätzung möglich ist
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."}
debug_print("Fehler bei der ChatGPT-Anfrage: " + str(e))
return {"branch": "k.A.", "consistency": "X", "justification": "ChatGPT-Anfrage fehlgeschlagen."}
# Parse die Antwort von ChatGPT
chat_branch = None
chat_consistency = None
chat_justification = ""
for line in result.split("\n"):
lower_line = line.lower()
if lower_line.startswith("branche:"):
chat_branch = line.split(":", 1)[1].strip()
elif lower_line.startswith("übereinstimmung:"):
chat_consistency = line.split(":", 1)[1].strip()
elif lower_line.startswith("begründung:"):
chat_justification = line.split(":", 1)[1].strip()
# Parsen der ChatGPT-Antwort
suggested_branch = ""
consistency = ""
justification = ""
for line in chat_output.split("\n"):
if line.startswith("Branche:"):
suggested_branch = line.split(":", 1)[1].strip()
elif line.startswith("Übereinstimmung:"):
consistency = line.split(":", 1)[1].strip()
elif line.startswith("Begründung:"):
justification = line.split(":", 1)[1].strip()
debug_print(f"Extrahiert: Branche='{suggested_branch}', Übereinstimmung='{consistency}', Begründung='{justification}'")
debug_print(f"Extrahiert: Branche='{chat_branch}', Übereinstimmung='{chat_consistency}', Begründung='{chat_justification}'")
# Normiere die Werte zum Vergleich
norm_crm = normalize_company_name(crm_branche)
norm_chat = normalize_company_name(chat_branch) if chat_branch else ""
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
if not norm_chat:
debug_print("Keine aussagekräftige ChatGPT-Antwort erhalten, fallback: CRM-Branche.")
if norm_crm in norm_targets:
return {"branch": crm_branche, "consistency": "ok", "justification": "Keine ChatGPT-Antwort; CRM-Wert übernommen."}
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.")
# Normalisiere die Werte (normalize_string muss in deinem Projekt vorhanden sein)
norm_crm = normalize_string(crm_branche)
norm_suggested = normalize_string(suggested_branch)
# Ü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.")
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:
debug_print("Fallback: Keine gültige Branche 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 = ""
return {"branch": "k.A.", "consistency": "X", "justification": "Kein gültiger Brancheneintrag gefunden."}
# Ü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:
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}
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):