Fokusbranchen-Logik in Branchenevaluation integriert

Wiedereinführung und Verbesserung der Funktionalität zur Priorisierung von Fokusbranchen bei der automatisierten Branchenklassifizierung durch ChatGPT.

**Änderungen:**
- **Fokusbranchen aus CSV:**
    - Die Definition von Fokusbranchen erfolgt nun über eine zusätzliche Spalte in der `ziel_Branchenschema.csv`-Datei. Dies ermöglicht eine flexible Konfiguration ohne Code-Änderungen.
    - Die Funktion `load_target_schema` wurde erweitert, um diese Fokus-Markierungen einzulesen und eine separate Liste `FOCUS_TARGET_BRANCHES` sowie einen spezifischen Prompt-Teil `FOCUS_BRANCHES_PROMPT_PART` zu generieren.
- **Angepasste `evaluate_branche_chatgpt` Funktion:**
    - Nutzt nun die global geladenen Listen `ALLOWED_TARGET_BRANCHES` und `FOCUS_TARGET_BRANCHES` sowie die Prompt-Teile `TARGET_SCHEMA_STRING` und `FOCUS_BRANCHES_PROMPT_PART`.
    - Der an ChatGPT gesendete Prompt wurde um einen expliziten Hinweis erweitert, Fokusbranchen bei der Klassifizierung zu priorisieren, falls mehrere Branchen plausibel erscheinen.
    - Die Fallback-Logik für die zu verwendende Beschreibungsquelle (CRM-Beschreibung vs. Website-Zusammenfassung bei fehlenden Wiki-Daten) wurde aus der früheren Funktionsversion übernommen und verfeinert.
    - Die API-Key-Handhabung wurde entfernt, da diese nun global über `Config.API_KEYS` und `call_openai_chat` erfolgt.
    - `debug_print` Aufrufe wurden durch Standard-Logging (`logger.debug`) ersetzt.
- **Globale Variablen:** Neue globale Variablen für Fokusbranchen und deren Prompt-Teil wurden eingeführt.

**Ziel:**
- Erhöhung der Genauigkeit der Branchenklassifizierung, indem vordefinierte, strategisch wichtige Branchen bei der KI-gestützten Bewertung bevorzugt werden.
- Verbesserung der Flexibilität und Wartbarkeit der Fokusbranchen-Definition durch Auslagerung in die zentrale CSV-Datei.
This commit is contained in:
2025-05-10 08:11:41 +00:00
parent 163dc7d220
commit 33f38cb96d

View File

@@ -191,6 +191,9 @@ COLUMN_MAP = {
BRANCH_MAPPING = {}
TARGET_SCHEMA_STRING = "Ziel-Branchenschema nicht verfuegbar."
ALLOWED_TARGET_BRANCHES = []
FOCUS_TARGET_BRANCHES = [] # NEU
TARGET_SCHEMA_STRING = "Ziel-Branchenschema nicht verfuegbar."
FOCUS_BRANCHES_PROMPT_PART = "" # NEU: Für den Prompt-Teil der Fokusbranchen
# Marker für URLs, die erneut per SERP gesucht werden sollen
URL_CHECK_MARKER = "URL_CHECK_NEEDED" # <<< NEU HINZUFÜGEN
@@ -975,91 +978,83 @@ ALLOWED_TARGET_BRANCHES = [] # Liste der erlaubten Kurzformen
def load_target_schema(csv_filepath=BRANCH_MAPPING_FILE):
"""
Laedt Liste erlaubter Ziel-Branchen (Kurzformen) aus Spalte A der CSV-Datei.
Befuellt die globalen Variablen ALLOWED_TARGET_BRANCHES und TARGET_SCHEMA_STRING.
Args:
csv_filepath (str, optional): Pfad zur CSV-Datei mit dem Branchenschema.
Defaults to the global BRANCH_MAPPING_FILE.
Laedt Liste erlaubter Ziel-Branchen und Fokus-Branchen aus der CSV-Datei.
Befuellt die globalen Variablen ALLOWED_TARGET_BRANCHES, FOCUS_TARGET_BRANCHES,
TARGET_SCHEMA_STRING und FOCUS_BRANCHES_PROMPT_PART.
"""
logger = logging.getLogger(__name__) # <<< DIESE ZEILE HINZUFÜGEN
# Zugriff auf die globalen Variablen
global BRANCH_MAPPING, TARGET_SCHEMA_STRING, ALLOWED_TARGET_BRANCHES
logger = logging.getLogger(__name__)
global ALLOWED_TARGET_BRANCHES, FOCUS_TARGET_BRANCHES, TARGET_SCHEMA_STRING, FOCUS_BRANCHES_PROMPT_PART
# Setzen Sie BRANCH_MAPPING zurueck, da es in dieser Version nicht primaer genutzt wird
BRANCH_MAPPING = {}
allowed_branches_set = set() # Nutzt ein Set, um Duplikate automatisch zu behandeln
ALLOWED_TARGET_BRANCHES = []
FOCUS_TARGET_BRANCHES = []
allowed_branches_set = set()
focus_branches_set = set() # Für Fokusbranchen
line_count = 0
logger.info(f"Lade Ziel-Schema (Kurzformen) aus '{csv_filepath}' Spalte A...") # Jetzt sollte logger definiert sein
logger.info(f"Lade Ziel-Schema und Fokus-Branchen aus '{csv_filepath}'...")
try:
# Versuche, die Datei mit UTF-8-BOM-Signatur oder normalem UTF-8 zu oeffnen
# Verwenden Sie "r" fuer Textmodus und geben Sie das Encoding an
with open(csv_filepath, "r", encoding="utf-8-sig") as f:
# Verwenden Sie den CSV-Reader, um Zeilen zu lesen
reader = csv.reader(f)
# Versuche, die erste Zeile als Header zu ueberspringen (Heuristik)
try:
header_row = next(reader)
# logger.debug(f"Ueberspringe Header-Zeile: {header_row}") # Zu viel Laerm im Debug
header_row = next(reader) # Überspringe Header
logger.debug(f"Ueberspringe Header-Zeile im Schema: {header_row}")
except StopIteration:
# Wenn die Datei leer ist
logger.warning(f"Schema-Datei '{csv_filepath}' ist leer.")
ALLOWED_TARGET_BRANCHES = [] # Setze die Liste auf leer
TARGET_SCHEMA_STRING = "Ziel-Branchenschema nicht verfuegbar (Datei leer)." # Setze Fehler-String
return # Beende die Funktion, da nichts zu tun ist
logger.warning(f"Schema-Datei '{csv_filepath}' ist leer oder hat keinen Header.")
TARGET_SCHEMA_STRING = "Ziel-Branchenschema nicht verfuegbar (Datei leer)."
FOCUS_BRANCHES_PROMPT_PART = ""
return
# Iteriere ueber die verbleibenden Zeilen
for row in reader:
line_count += 1
# logger.debug(f"Schema-Laden: Lese Zeile {line_count}: {row}") # Zu viel Laerm im Debug
for row_num, row in enumerate(reader, 1): # Starte Zählung bei 1 für Zeilennummern nach Header
line_count = row_num
if not row: # Leere Zeile überspringen
continue
# Pruefe, ob die Zeile mindestens eine Spalte hat (Spalte A ist Index 0)
if len(row) >= 1:
target = row[0].strip() # Hole den Wert aus Spalte A und entferne Whitespace
if target: # Fuege den Wert zum Set hinzu, wenn er nicht leer ist
allowed_branches_set.add(target)
# logger.debug(f" -> '{target}' zum Set hinzugefuegt.") # Zu viel Laerm im Debug
target_branch = row[0].strip()
if target_branch:
allowed_branches_set.add(target_branch)
# Prüfe Spalte B (Index 1) für Fokus-Markierung
if len(row) >= 2 and row[1].strip().upper() in ["X", "FOKUS", "JA", "TRUE", "1"]:
focus_branches_set.add(target_branch)
logger.debug(f" -> Fokusbranche gefunden: '{target_branch}'")
except FileNotFoundError:
# Wenn die Schema-Datei nicht gefunden wird
logger.critical(f"FEHLER: Schema-Datei '{csv_filepath}' nicht gefunden.")
ALLOWED_TARGET_BRANCHES = [] # Setze die Liste auf leer
TARGET_SCHEMA_STRING = "Ziel-Branchenschema nicht verfuegbar (Datei nicht gefunden)." # Setze Fehler-String
return # Beende die Funktion, da die Datei fehlt
TARGET_SCHEMA_STRING = "Ziel-Branchenschema nicht verfuegbar (Datei nicht gefunden)."
FOCUS_BRANCHES_PROMPT_PART = ""
return
except Exception as e:
# Fange andere unerwartete Fehler beim Lesen der Datei ab
logger.critical(f"FEHLER beim Laden des Ziel-Schemas aus '{csv_filepath}' (Zeile {line_count if line_count > 0 else 'vor erster Zeile'}): {e}")
ALLOWED_TARGET_BRANCHES = [] # Setze die Liste auf leer
TARGET_SCHEMA_STRING = "Ziel-Branchenschema nicht verfuegbar (Fehler beim Lesen)." # Setze Fehler-String
return # Beende die Funktion, da ein Fehler aufgetreten ist
logger.critical(f"FEHLER beim Laden des Ziel-Schemas aus '{csv_filepath}' (Zeile {line_count}): {e}")
TARGET_SCHEMA_STRING = "Ziel-Branchenschema nicht verfuegbar (Fehler beim Lesen)."
FOCUS_BRANCHES_PROMPT_PART = ""
return
# Konvertiere das Set in eine sortierte Liste
ALLOWED_TARGET_BRANCHES = sorted(list(allowed_branches_set), key=str.lower)
logger.info(f"Ziel-Schema geladen. {len(ALLOWED_TARGET_BRANCHES)} eindeutige Zielbranchen gefunden.")
FOCUS_TARGET_BRANCHES = sorted(list(focus_branches_set), key=str.lower)
logger.info(f"Ziel-Schema geladen: {len(ALLOWED_TARGET_BRANCHES)} eindeutige Zielbranchen, davon {len(FOCUS_TARGET_BRANCHES)} Fokusbranchen.")
# Erstelle den Prompt-String fuer ChatGPT, wenn gueltige Branchen gefunden wurden
if ALLOWED_TARGET_BRANCHES:
# logger.debug(f"Erste 10 geladene Zielbranchen: {ALLOWED_TARGET_BRANCHES[:10]}") # Zu viel Laerm im Debug
schema_lines = ["Ziel-Branchenschema: Folgende Branchenbereiche sind gueltig (Kurzformen):"]
# Fuege jede erlaubte Branche als Listeneintrag hinzu
schema_lines.extend(f"- {branch}" for branch in ALLOWED_TARGET_BRANCHES)
# Fuege strenge Anweisungen fuer das Antwortformat hinzu
schema_lines.append("\nBitte ordne das Unternehmen ausschliesslich in einen dieser Bereiche ein. Gib NUR den exakten Kurznamen der Branche zurueck (keine Praefixe oder zusaetzliche Erklaerungen ausser im 'Begruendung'-Feld).") # Verwende Umlaute nicht, um Encoding-Probleme im Prompt zu vermeiden
# Anweisungen für das Antwortformat (unverändert)
schema_lines.append("\nBitte ordne das Unternehmen ausschliesslich in einen dieser Bereiche ein. Gib NUR den exakten Kurznamen der Branche zurueck (keine Praefixe oder zusaetzliche Erklaerungen ausser im 'Begruendung'-Feld).")
schema_lines.append("Antworte ausschliesslich im folgenden Format (keine Einleitung, kein Schlusssatz):")
schema_lines.append("Branche: <Exakter Kurzname der Branche aus der Liste>")
schema_lines.append("Uebereinstimmung: <ok oder X (Vergleich deines Vorschlags mit der extrahierten Kurzform der CRM-Referenz)>") # Verwende Umlaute nicht
schema_lines.append("Begruendung: <Sehr kurze Begruendung fuer deinen Branchenvorschlag>") # Verwende Umlaute nicht
# Verbinde die Zeilen zum finalen Prompt-String
schema_lines.append("Uebereinstimmung: <ok oder X (Vergleich deines Vorschlags mit der extrahierten Kurzform der CRM-Referenz)>")
schema_lines.append("Begruendung: <Sehr kurze Begruendung fuer deinen Branchenvorschlag>")
TARGET_SCHEMA_STRING = "\n".join(schema_lines)
# logger.debug(f"Generierter TARGET_SCHEMA_STRING:\n{TARGET_SCHEMA_STRING}") # Zu viel Laerm im Debug
if FOCUS_TARGET_BRANCHES:
focus_prompt_lines = ["\nZusätzlicher Hinweis: Wenn die Wahl zwischen mehreren passenden Branchen besteht, priorisiere bitte, wenn möglich, eine der folgenden Fokusbranchen:"]
focus_prompt_lines.extend(f"- {branch}" for branch in FOCUS_TARGET_BRANCHES)
FOCUS_BRANCHES_PROMPT_PART = "\n".join(focus_prompt_lines)
else:
FOCUS_BRANCHES_PROMPT_PART = ""
logger.info("Keine Fokusbranchen im Schema definiert.")
else:
# Wenn keine gueltigen Branchen gefunden wurden
TARGET_SCHEMA_STRING = "Ziel-Branchenschema nicht verfuegbar (Keine gueltigen Branchen in Datei gefunden)."
FOCUS_BRANCHES_PROMPT_PART = ""
logger.warning("Keine gueltigen Zielbranchen im Schema gefunden. Branchenbewertung ist nicht moeglich.")
@@ -1404,201 +1399,156 @@ def summarize_batch_openai(tasks_data):
# Funktion zur Branchenbewertung mittels OpenAI.
# Nutzt globale Helfer: ALLOWED_TARGET_BRANCHES, TARGET_SCHEMA_STRING,
# call_openai_chat, logger, re, retry_on_failure.
@retry_on_failure # Wende den Decorator auf diese Funktion an, da sie call_openai_chat aufruft
@retry_on_failure
def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kategorien, website_summary):
"""
Ordnet das Unternehmen basierend auf den angegebenen Informationen exakt einer Branche
aus dem Ziel-Branchenschema (nur Kurzformen) zu. Validiert den ChatGPT-Vorschlag
strikt gegen die erlaubten Kurzformen und fuehrt einen Fallback auf die (extrahierte)
CRM-Kurzform durch, falls der Vorschlag ungueltig ist.
logger = logging.getLogger(__name__)
# Zugriff auf die globalen, durch load_target_schema() befüllten Variablen
global ALLOWED_TARGET_BRANCHES, TARGET_SCHEMA_STRING, FOCUS_BRANCHES_PROMPT_PART
Args:
crm_branche (str): Branche laut CRM (kann noch Praefix enthalten).
beschreibung (str): Unternehmensbeschreibung (CRM).
wiki_branche (str): Branche aus Wikipedia (falls vorhanden).
wiki_kategorien (str): Wikipedia-Kategorien.
website_summary (str): Zusammenfassung des Website-Inhalts.
Returns:
dict: Enthaehlt "branch" (die finale, gueltige Kurzform oder Fehler),
"consistency" ('ok', 'X', 'fallback_crm_valid', 'fallback_invalid', 'error_...'),
"justification" (Begruendung von ChatGPT oder Fallback-Info).
Wirft Exception bei API-Fehlern nach Retries (von call_openai_chat).
"""
# Verwenden Sie logger, da das Logging jetzt konfiguriert ist
logger = logging.getLogger(__name__) # <<< DIESE ZEILE HINZUFÜGEN
# Zugriff auf globale Variablen (befuellt von load_target_schema im Block 6)
global ALLOWED_TARGET_BRANCHES, TARGET_SCHEMA_STRING
# Grundlegende Pruefung: Ist das Schema ueberhaupt geladen?
if not ALLOWED_TARGET_BRANCHES:
logger.critical("FEHLER in evaluate_branche_chatgpt: Ziel-Branchenschema (ALLOWED_TARGET_BRANCHES) ist leer. Kann Branchen nicht validieren.")
# Geben Sie ein Fehlerergebnis zurueck
logger.critical("FEHLER in evaluate_branche_chatgpt: Ziel-Branchenschema nicht geladen.")
return {"branch": "FEHLER - SCHEMA FEHLT", "consistency": "error_schema_missing", "justification": "Fehler: Ziel-Schema nicht geladen"}
# Erstelle Lookup fuer erlaubte Branches (case-insensitive)
allowed_branches_lookup = {b.lower(): b for b in ALLOWED_TARGET_BRANCHES}
# --- Prompt fuer ChatGPT erstellen ---
# Beginne mit den Regeln und der Liste der gueltigen Kurzformen
prompt_parts = [TARGET_SCHEMA_STRING] # Enthält bereits die Liste und Anweisungen
# --- Prompt für ChatGPT erstellen ---
# Beginnt mit den Regeln und der Liste der gueltigen Kurzformen (TARGET_SCHEMA_STRING)
# Fügt dann den Hinweis auf Fokusbranchen hinzu (FOCUS_BRANCHES_PROMPT_PART)
prompt_parts = [TARGET_SCHEMA_STRING, FOCUS_BRANCHES_PROMPT_PART] # <<< HIER FOCUS_BRANCHES_PROMPT_PART EINFÜGEN
prompt_parts.append("\nOrdne das Unternehmen anhand folgender Angaben exakt einer Branche des Ziel-Branchenschemas (Kurzformen) zu:")
# Fuege nur vorhandene Informationen hinzu und kuerze sie ggf.
# Stellen Sie sicher, dass die Werte keine None-Typen sind
# Informationen hinzufügen (unverändert)
if crm_branche and str(crm_branche).strip() and str(crm_branche).strip().lower() != "k.a.": prompt_parts.append(f"- CRM-Branche (Referenz): {str(crm_branche).strip()}")
if beschreibung and str(beschreibung).strip() and str(beschreibung).strip().lower() != "k.a.": prompt_parts.append(f"- Beschreibung: {str(beschreibung).strip()[:500]}...") # Kuerzen
if wiki_branche and str(wiki_branche).strip() and str(wiki_branche).strip().lower() != "k.a.": prompt_parts.append(f"- Wikipedia-Branche: {str(wiki_branche).strip()[:300]}...") # Kuerzen
if wiki_kategorien and str(wiki_kategorien).strip() and str(wiki_kategorien).strip().lower() != "k.a.": prompt_parts.append(f"- Wikipedia-Kategorien: {str(wiki_kategorien).strip()[:500]}...") # Kuerzen
if website_summary and str(website_summary).strip() and str(website_summary).strip().lower() != "k.a." and not str(website_summary).strip().startswith("k.A. (Fehler"): # Pruefe auch auf Website Summary Fehlerwerte
prompt_parts.append(f"- Website-Zusammenfassung: {str(website_summary).strip()[:500]}...") # Kuerzen
# Fallback-Logik für beschreibung vs. website_summary (basierend auf Ihrer alten Funktion)
if wiki_branche and str(wiki_branche).strip() and str(wiki_branche).strip().lower() != "k.a.":
# Wenn Wiki-Daten vorhanden sind, nutzen wir die CRM-Beschreibung als Hauptbeschreibung
if beschreibung and str(beschreibung).strip() and str(beschreibung).strip().lower() != "k.a.":
prompt_parts.append(f"- Beschreibung (CRM): {str(beschreibung).strip()[:500]}...")
if website_summary and str(website_summary).strip() and str(website_summary).strip().lower() != "k.a." and not str(website_summary).strip().startswith("k.A. (Fehler"):
prompt_parts.append(f"- Website-Zusammenfassung: {str(website_summary).strip()[:500]}...")
prompt_parts.append(f"- Wikipedia-Branche: {str(wiki_branche).strip()[:300]}...")
if wiki_kategorien and str(wiki_kategorien).strip() and str(wiki_kategorien).strip().lower() != "k.a.":
prompt_parts.append(f"- Wikipedia-Kategorien: {str(wiki_kategorien).strip()[:500]}...")
else: # Keine validen Wiki-Daten
logger.debug("evaluate_branche_chatgpt: Keine validen Wiki-Daten, nutze Website-Zusammenfassung als primäre Beschreibung (falls vorhanden).")
if website_summary and str(website_summary).strip() and str(website_summary).strip().lower() != "k.a." and not str(website_summary).strip().startswith("k.A. (Fehler"):
prompt_parts.append(f"- Website-Zusammenfassung (als Hauptbeschreibung): {str(website_summary).strip()[:800]}...") # Ggf. länger, da Hauptinfo
elif beschreibung and str(beschreibung).strip() and str(beschreibung).strip().lower() != "k.a.": # Fallback auf CRM Beschreibung
prompt_parts.append(f"- Beschreibung (CRM, als Hauptbeschreibung): {str(beschreibung).strip()[:800]}...")
# Fallback, wenn zu wenige Infos da sind (mindestens 2 relevante Zeilen im Prompt neben dem Schema)
# Der Prompt hat immer mindestens 1 Zeile (Schema) + 1 Zeile (Instruktion "Ordne zu...").
# Pruefen wir, ob mindestens 2 Info-Zeilen hinzugefuegt wurden.
if len(prompt_parts) < 3: # 1 (Schema) + 1 (Instruktion) + <2 (Infos)
logger.warning("Warnung in evaluate_branche_chatgpt: Zu wenige Informationen (<2 Quellen) fuer Branchenevaluierung.")
# Geben Sie ein Fehlerergebnis zurueck, verwenden Sie die CRM-Branche als Fallback
return {"branch": crm_branche, "consistency": "error_no_info", "justification": "Fehler: Zu wenige Informationen fuer eine Einschaetzung"}
if len(prompt_parts) < (3 + (1 if FOCUS_BRANCHES_PROMPT_PART else 0) )): # Mind. TARGET_SCHEMA + FOCUS_PART (falls vorhanden) + Instruktion + 1 Infoquelle
logger.warning("Warnung in evaluate_branche_chatgpt: Zu wenige Informationen für Branchenevaluierung.")
crm_short_branch_for_fallback = "k.A."
if crm_branche and isinstance(crm_branche, str):
parts = crm_branche.split(">", 1)
crm_short_branch_for_fallback = parts[1].strip() if len(parts) > 1 else crm_branche.strip()
return {"branch": crm_short_branch_for_fallback if crm_short_branch_for_fallback.lower() != "k.a." else "FEHLER",
"consistency": "error_no_info",
"justification": "Fehler: Zu wenige Informationen fuer eine Einschaetzung"}
# Prompt fuer das Antwortformat ist bereits in TARGET_SCHEMA_STRING enthalten.
prompt = "\n".join(filter(None, prompt_parts)) # filter(None, ...) um leere Strings aus FOCUS_BRANCHES_PROMPT_PART zu entfernen
# logger.debug(f"Erstellter Prompt fuer Branchenevaluierung:\n---\n{prompt}\n---")
prompt = "\n".join(prompt_parts)
# logger.debug(f"Erstellter Prompt fuer Branchenevaluierung:\n---\n{prompt}\n---") # Zu viel Laerm im Debug
# --- ChatGPT aufrufen (Rest der Funktion bleibt wie in Block 10/20) ---
# ... (ChatGPT Call, Parsing, Validierung, Fallback-Logik) ...
# Wichtig: Die API-Key Ladung und der openai-Import sollten jetzt global (Block 1) erfolgen
# und nicht mehr innerhalb dieser Funktion.
# Ihre alte Funktion hat api_key.txt gelesen, das ist jetzt Teil von Config.load_api_keys().
# --- ChatGPT aufrufen ---
# call_openai_chat nutzt den retry_on_failure Decorator und wirft bei endgueltigem Fehler eine Exception
chat_response = None
try:
chat_response = call_openai_chat(prompt, temperature=0.0) # Niedrige Temperatur fuer konsistente Zuordnung
chat_response = call_openai_chat(prompt, temperature=0.0) # Nutzt globale Funktion
if not chat_response:
# Dieser Fall sollte nach der Aenderung in call_openai_chat nicht mehr auftreten (wuerde Exception werfen)
logger.error("call_openai_chat gab unerwarteterweise None zurueck fuer Branchenevaluation.")
raise openai.error.APIError("Keine Antwort von OpenAI erhalten fuer Branchenevaluation.") # Wirf eine Exception
logger.error("call_openai_chat gab unerwarteterweise None/leer zurueck fuer Branchenevaluation.")
raise APIError("Keine Antwort von OpenAI erhalten fuer Branchenevaluation.")
except Exception as e:
# Wenn call_openai_chat nach Retries eine Exception wirft
# Der Fehler wird bereits vom retry_on_failure Decorator geloggt.
logger.error(f"Endgueltiger FEHLER beim OpenAI-Aufruf fuer Branchenevaluation: {e}")
# Geben Sie ein Fehlerergebnis zurueck, verwenden Sie die CRM-Branche als Fallback
# Haengen Sie die Fehlermeldung an die Begruendung an.
return {"branch": crm_branche, "consistency": "error_api_failed", "justification": f"Fehler API: {str(e)[:100]}"}
crm_short_branch_for_fallback = "k.A."
if crm_branche and isinstance(crm_branche, str):
parts = crm_branche.split(">", 1)
crm_short_branch_for_fallback = parts[1].strip() if len(parts) > 1 else crm_branche.strip()
return {"branch": crm_short_branch_for_fallback if crm_short_branch_for_fallback.lower() != "k.a." else "FEHLER API",
"consistency": "error_api_failed",
"justification": f"Fehler API: {str(e)[:100]}"}
# --- Antwort parsen ---
# --- Antwort parsen (wie gehabt) ---
lines = chat_response.strip().split("\n")
# Initialisiere Ergebnisdict mit Fallback-Werten oder leeren Strings
result = {"branch": None, "consistency": None, "justification": ""}
suggested_branch = ""
parsed_branch = False
for line in lines:
line_lower = line.lower(); line_stripped = line.strip()
if line_lower.startswith("branche:"):
# Extrahiere die vorgeschlagene Branche, bereinige Leerzeichen und Anfuehrungszeichen
suggested_branch = line_stripped.split(":", 1)[1].strip().strip('"\'')
parsed_branch = True
elif line_lower.startswith("uebereinstimmung:") or line_lower.startswith("ubereinstimmung:"): # Beruecksichtige Umlaute und keine Umlaute
# Wir ueberschreiben die Konsistenz spaeter basierend auf unserer Logik, ignorieren Sie die KI-Antwort hier
elif line_lower.startswith("uebereinstimmung:") or line_lower.startswith("ubereinstimmung:"):
pass
elif line_lower.startswith("begruendung:") or line_lower.startswith("begruendung:"): # Beruecksichtige Umlaute und keine Umlaute
# Erfasse die Begruendung. Wenn es mehrere Begruendungszeilen gibt, haenge sie an.
elif line_lower.startswith("begruendung:") or line_lower.startswith("begruendung:"):
justification_text = line_stripped.split(":", 1)[1].strip()
if result["justification"]: result["justification"] += " " + justification_text
else: result["justification"] = justification_text
# Behandle andere moegliche unerwartete Zeilen (optional)
# elif line_lower.startswith(("resultat", "eintrag", "antwort")):
# logger.warning(f"Unerwartete Zeile im Branchen-Prompt gefunden: {line[:100]}...")
if not parsed_branch or not suggested_branch or suggested_branch.lower() in ["k.a.", "n/a"]:
logger.error(f"Fehler in evaluate_branche_chatgpt: Konnte 'Branche:' nicht oder nur leer/k.A. aus Antwort parsen: {chat_response[:500]}...")
crm_short_branch_for_fallback = "k.A."
if crm_branche and isinstance(crm_branche, str):
parts = crm_branche.split(">", 1)
crm_short_branch_for_fallback = parts[1].strip() if len(parts) > 1 else crm_branche.strip()
return {"branch": crm_short_branch_for_fallback if crm_short_branch_for_fallback.lower() != "k.a." else "FEHLER PARSING",
"consistency": "error_parsing",
"justification": f"Fehler Parsing: Antwortformat unerwartet."}
# Pruefe, ob Branch geparst wurde UND nicht leer ist
if not parsed_branch or not suggested_branch or suggested_branch.lower() in ["k.a.", "n/a"]: # Fuege "k.a." zur Pruefung hinzu
logger.error(f"Fehler in evaluate_branche_chatgpt: Konnte 'Branche:' nicht oder nur leer/k.A. aus Antwort parsen: {chat_response[:500]}...") # Logge Anfang der Antwort
# Geben Sie ein Fehlerergebnis zurueck, verwenden Sie die CRM-Branche als Fallback
return {"branch": crm_branche, "consistency": "error_parsing", "justification": f"Fehler Parsing: Antwortformat unerwartet."}
# --- Validierung des ChatGPT-Vorschlags ---
# --- Validierung und Fallback (wie gehabt) ---
final_branch = None
suggested_branch_lower = suggested_branch.lower()
# 1. Ist der vorgeschlagene Branch EXAKT im Ziel-Schema enthalten?
if suggested_branch_lower in allowed_branches_lookup:
final_branch = allowed_branches_lookup[suggested_branch_lower] # Nimm die korrekte Schreibweise aus der Liste
final_branch = allowed_branches_lookup[suggested_branch_lower]
logger.debug(f"ChatGPT-Branchenvorschlag '{suggested_branch}' ist gueltig ('{final_branch}').")
result["consistency"] = "pending_comparison" # Temporaer Status vor Vergleich mit CRM
result["consistency"] = "pending_comparison"
else:
# --- Fallback-Logik, wenn Vorschlag ungueltig ist ---
logger.debug(f"ChatGPT-Branchenvorschlag '{suggested_branch}' ist NICHT im Ziel-Schema ({len(ALLOWED_TARGET_BRANCHES)} Eintraege). Starte Fallback...")
# Versuche Kurzform aus CRM-Branche zu extrahieren
crm_short_branch = "k.A." # Default
# Stellen Sie sicher, dass crm_branche ein String ist
if crm_branche and isinstance(crm_branche, str) and ">" in crm_branche:
crm_short_branch = crm_branche.split(">", 1)[1].strip()
# Wenn CRM schon Kurzform sein koennte (nicht leer/k.A. und kein Praefix > enthalten)
elif crm_branche and isinstance(crm_branche, str) and crm_branche.strip() and crm_branche.strip().lower() != "k.a.":
crm_short_branch = crm_branche.strip()
logger.debug(f" Fallback: Pruefe extrahierte CRM-Kurzform: '{crm_short_branch}'")
logger.debug(f"ChatGPT-Branchenvorschlag '{suggested_branch}' ist NICHT im Ziel-Schema. Starte Fallback...")
crm_short_branch = "k.A."
if crm_branche and isinstance(crm_branche, str):
parts = crm_branche.split(">", 1)
crm_short_branch = parts[1].strip() if len(parts) > 1 else crm_branche.strip()
crm_short_branch_lower = crm_short_branch.lower()
# 2. Ist die extrahierte CRM-Kurzform EXAKT im Ziel-Schema enthalten?
if crm_short_branch != "k.A." and crm_short_branch_lower in allowed_branches_lookup:
final_branch = allowed_branches_lookup[crm_short_branch_lower] # Nimm korrekte Schreibweise
result["consistency"] = "fallback_crm_valid" # Setze Fallback-Status
# Kombiniere ChatGPT Begruendung (falls vorhanden) mit Fallback-Info
final_branch = allowed_branches_lookup[crm_short_branch_lower]
result["consistency"] = "fallback_crm_valid"
fallback_reason = f"Fallback: Ungueltiger ChatGPT-Vorschlag ('{suggested_branch}'). Gueltige CRM-Kurzform '{final_branch}' verwendet."
result["justification"] = f"{fallback_reason} (ChatGPT Begruendung war: {result.get('justification', 'Keine')})"
logger.info(f"Fallback auf gueltige CRM-Kurzform erfolgreich: '{final_branch}'")
else:
# 3. Wenn auch CRM-Kurzform ungueltig
final_branch = suggested_branch # Behalte ungueltigen Vorschlag
result["consistency"] = "fallback_invalid" # Setze Fehler-Fallback-Status
error_reason = f"Fehler: Ungueltiger ChatGPT-Vorschlag ('{suggested_branch}') und keine gueltige CRM-Kurzform ('{crm_short_branch}') als Fallback verfuegbar."
final_branch = suggested_branch # Behalte ungueltigen Vorschlag für Info, wird aber als Fehler markiert
result["consistency"] = "fallback_invalid"
error_reason = f"Fehler: Ungueltiger ChatGPT-Vorschlag ('{suggested_branch}') und keine gueltige CRM-Kurzform ('{crm_short_branch}') als Fallback."
result["justification"] = f"{error_reason} (ChatGPT Begruendung war: {result.get('justification', 'Keine')})"
logger.warning(f"Fallback fehlgeschlagen. Ungueltiger Vorschlag: '{final_branch}', Ungueltige CRM-Kurzform: '{crm_short_branch}'")
# Alternativ: Setze final_branch auf einen expliziten Fehlerwert, um es im Sheet hervorzuheben
# final_branch = "FEHLER - UNGUELTIGE ZUWEISUNG" # Optional
final_branch = "FEHLER - UNGUELTIGE ZUWEISUNG" # Expliziter Fehlerwert
# Setze den finalen Branch im Ergebnis-Dictionary
# Verwenden Sie einen Standard-Fehlerwert, falls final_branch aus irgendeinem Grund immer noch None ist
result["branch"] = final_branch if final_branch else "FEHLER"
# --- Konsistenzpruefung (Finale Bewertung des final_branch vs. CRM-Kurzform) ---
# Extrahiere CRM-Kurzform fuer den Vergleich (erneut oder Variable von oben)
crm_short_to_compare = "k.A."
if crm_branche and isinstance(crm_branche, str) and ">" in crm_branche:
crm_short_to_compare = crm_branche.split(">", 1)[1].strip()
elif crm_branche and isinstance(crm_branche, str) and crm_branche.strip() and crm_branche.strip().lower() != "k.a.":
crm_short_to_compare = crm_branche.strip()
if crm_branche and isinstance(crm_branche, str):
parts = crm_branche.split(">", 1)
crm_short_to_compare = parts[1].strip() if len(parts) > 1 else crm_branche.strip()
# Vergleiche finalen Branch (falls nicht FEHLER) mit CRM-Kurzform (case-insensitive)
# Aktualisiere den Consistency-Status, WENN er noch 'pending_comparison' ist.
# Fallback-Status ('fallback_crm_valid', 'fallback_invalid') sollen erhalten bleiben.
if result["consistency"] == "pending_comparison" and result["branch"] != "FEHLER":
if result["consistency"] == "pending_comparison" and result["branch"] != "FEHLER" and not result["branch"].startswith("FEHLER"):
if result["branch"].lower() == crm_short_to_compare.lower():
result["consistency"] = "ok" # Uebereinstimmung mit CRM
result["consistency"] = "ok"
else:
result["consistency"] = "X" # Keine Uebereinstimmung mit CRM
# Entferne den temporaeren Status, falls er noch da ist (sollte nicht passieren)
result["consistency"] = "X"
if result["consistency"] == "pending_comparison":
logger.warning("Konsistenzpruefung blieb im Status 'pending_comparison', setze auf 'error_comparison_failed'.")
result["consistency"] = "error_comparison_failed"
elif result["consistency"] is None: # Sollte nicht passieren
logger.error("Konsistenz blieb unerwartet None, setze auf 'error_unknown_state'.")
elif result["consistency"] is None:
result["consistency"] = "error_unknown_state"
# Debug-Ausgabe des finalen Ergebnisses vor Rueckgabe
logger.debug(f"Finale Branch-Evaluation Ergebnis: Branch='{result.get('branch')}', Consistency='{result.get('consistency')}', Justification='{result.get('justification', '')[:100]}...'")
return result # Rueckgabe des Ergebnis-Dictionarys
return result
# ==============================================================================