diff --git a/brancheneinstufung.py b/brancheneinstufung.py index 673abccd..1b8359dd 100644 --- a/brancheneinstufung.py +++ b/brancheneinstufung.py @@ -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: ") - schema_lines.append("Uebereinstimmung: ") # Verwende Umlaute nicht - schema_lines.append("Begruendung: ") # Verwende Umlaute nicht - - # Verbinde die Zeilen zum finalen Prompt-String + schema_lines.append("Uebereinstimmung: ") + schema_lines.append("Begruendung: ") 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 # ==============================================================================