diff --git a/helpers.py b/helpers.py index 4207819c..3dc59f15 100644 --- a/helpers.py +++ b/helpers.py @@ -1001,161 +1001,137 @@ def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kateg @retry_on_failure def verify_wiki_article_chatgpt(company_name, website, wiki_url): - """ - Überprüft mittels ChatGPT, ob ein gegebener Wikipedia-Artikel zum Unternehmen passt. - Der Prompt ist nun intelligenter, um pedantische Ablehnungen zu vermeiden. - """ logger = logging.getLogger(__name__) - logger.info(f"Starte ChatGPT-Verifizierung für '{company_name}' und URL '{wiki_url[:50]}...'") + logger.info(f"Starte ChatGPT-Verifizierung für '{company_name}' und URL '{wiki_url[:50]}'...") - # NEUER, VERBESSERTER PROMPT - prompt = f""" - Aufgabe: Bewerte als erfahrener Datenanalyst, ob der Wikipedia-Artikel die relevanteste verfügbare Seite für das angegebene Unternehmen ist. - **Deine Bewertungsregeln:** - 1. **Muttergesellschaft ist OK:** Der Artikel darf auch die Muttergesellschaft beschreiben (z.B. für "ABB Automation GmbH" ist der Artikel "ABB (Unternehmen)" korrekt). - 2. **Rechtsform ist sekundär:** Ignoriere kleine Abweichungen im Namen, insbesondere fehlende Rechtsformen wie "GmbH", "AG" etc. (z.B. für "1&1 Versatel GmbH" ist der Artikel "1&1 Versatel" korrekt). - 3. **Inhalt zählt:** Prüfe, ob die Website des Unternehmens im Artikel erwähnt wird oder ob der beschriebene Geschäftszweck übereinstimmt. Das ist wichtiger als der exakte Name. + prompt = """Aufgabe: Bewerte als erfahrener Datenanalyst, ob der Wikipedia-Artikel die relevanteste verfügbare Seite für das angegebene Unternehmen ist. - **Unternehmen:** - - Name: {company_name} - - Website: {website} +Deine Bewertungsregeln: +1. Muttergesellschaft ist OK: Der Artikel darf auch die Muttergesellschaft beschreiben (z.B. für "ABB Automation GmbH" ist der Artikel "ABB (Unternehmen)" korrekt). +2. Rechtsform ist sekundär: Ignoriere kleine Abweichungen im NAmen, insbesondere fehlende REchtsformen wie "GmbH", "AG" etc. (z.B. für "1&1 Versatel GmbH" ist der Artikel "1&1 Versatel" korrekt). +3. Inhalt zählt: Prüfe, ob die Website des Unternehmens im Artikel erwähnt wird oder ob der beschriebene Geschäftszweck übereinstimmt. Das ist wichtiger als der exakte Name. - **Vorgegebener Wikipedia-Artikel:** - - URL: {wiki_url} +Unternehmen: +Name: {company_name} +Website: {website} - **Deine Antwort (antworte ausschließlich in diesem Format):** - Konsistenz: - Begründung: - Vorschlag URL: - """ - +Vorgegebener Wikipedia-Artikel: +Artikel: {wiki_url} + +Antwortformat: +Konsistenz: +Begründung: +Vorschlag URL: +""".strip() try: chat_response = call_openai_chat(prompt, temperature=0.0) if not chat_response: raise APIError("Keine Antwort von OpenAI für Wiki-Verifizierung erhalten.") - # Robustes Parsing der Antwort - result = {"consistency": "FEHLER PARSING", "justification": f"Unerwartete Antwort: {chat_response[:100]}", "suggested_url": ""} - lines = chat_response.strip().split("\n") - - parsed_data = {} - for line in lines: - if ":" in line: - key, value = line.split(":", 1) - key = key.strip().lower().replace(" ", "_") - value = value.strip() - parsed_data[key] = value - result["consistency"] = parsed_data.get("konsistenz", "FEHLER PARSING").upper() - result["justification"] = parsed_data.get("begründung", f"Begründung nicht gefunden in: {chat_response[:100]}") - result["suggested_url"] = parsed_data.get("vorschlag_url", "") - - logger.debug(f"Geparstes Wiki-Verify Ergebnis: {result}") - return result + # Robustes Parsing + parsed = {"consistency": "FEHLER PARSING", "justification": "", "suggested_url": ""} + for line in chat_response.strip().splitlines(): + if ":" in line: + key, val = line.split(":", 1) + key = key.strip().lower().replace(" ", "_") + parsed[key] = val.strip() + + + return { + "consistency": parsed.get("konsistenz", "FEHLER PARSING").upper(), + "justification": parsed.get("begründung", ""), + "suggested_url": parsed.get("vorschlag_url", ""), + } + except Exception as e: logger.error(f"Fehler bei der ChatGPT Wiki-Verifizierung: {e}") return {"consistency": "FEHLER API", "justification": str(e), "suggested_url": ""} - # ============================================================================== - # Chat GPT FSM Pitch - # ============================================================================== - @retry_on_failure -def generate_fsm_pitch(company_name, company_short_name, ki_branche, website_summary, wiki_absatz, anzahl_ma, anzahl_techniker, techniker_bucket_ml): +def generate_fsm_pitch( + company_name, + company_short_name, + ki_branche, + website_summary, + wiki_absatz, + anzahl_ma, + anzahl_techniker, + techniker_bucket_ml, +): """ Generiert einen maßgeschneiderten, nicht-werblichen Satz, der eine operative Service-Herausforderung des Unternehmens beschreibt (v2.1). """ logger = logging.getLogger(__name__) - # 1. Daten-Check: Genug Futter für einen guten Satz? - beschreibung_kombiniert = [] + # 1. Daten-Check + parts = [] if website_summary and 'k.a.' not in website_summary.lower(): - beschreibung_kombiniert.append(f"Website-Zusammenfassung: {website_summary}") + parts.append(f"Website-Zusammenfassung: {website_summary}") if wiki_absatz and 'k.a.' not in wiki_absatz.lower(): - beschreibung_kombiniert.append(f"Wikipedia-Einleitung: {wiki_absatz}") - - final_beschreibung = "\n".join(beschreibung_kombiniert) - - if not final_beschreibung or len(final_beschreibung.split()) < 10: - logger.warning(f"Zu wenige Informationen für FSM-Pitch für {company_name}. Breche ab.") + parts.append(f"Wikipedia-Einleitung: {wiki_absatz}") + combined = "\n".join(parts) + + if len(combined.split()) < 10: + logger.warning(f"Zu wenige Informationen für FSM-Pitch bei {company_name}.") return "FEHLER (Mangelnde Daten)" - # 2. Firmennamen priorisieren: Kurzform > Langform + # 2. Namenswahl display_name = company_short_name if company_short_name and company_short_name.lower() != 'k.a.' else company_name - # 3. Mitarbeiter-/Techniker-Info aufbereiten und priorisieren + # 3. Personalinfo def round_number(n): if n < 100: return n if n < 1000: return int(round(n / 50.0)) * 50 return int(round(n / 100.0)) * 100 - personal_info = "in einem Unternehmen Ihrer Größe" # Default + personal_info = "in einem Unternehmen Ihrer Größe" try: - # Priorität 1: Explizite Technikerzahl - if anzahl_techniker and int(anzahl_techniker) > 0: - anzahl_techniker = round_number(int(anzahl_techniker)) - personal_info = f"bei rund {anzahl_techniker} Servicetechnikern" - # Priorität 2: ML-Techniker-Bucket (wenn keine exakte Zahl vorhanden) - elif techniker_bucket_ml and 'k.a.' not in techniker_bucket_ml.lower() and anzahl_techniker == 0: + tech = int(anzahl_techniker or 0) + if tech > 0: + personal_info = f"bei rund {round_number(tech)} Servicetechnikern" + elif techniker_bucket_ml and 'k.a.' not in techniker_bucket_ml.lower(): personal_info = f"bei schätzungsweise {techniker_bucket_ml} Servicetechnikern" - # Priorität 3: Gesamtmitarbeiterzahl - elif anzahl_ma and int(anzahl_ma) > 0: - anzahl_ma = round_number(int(anzahl_ma)) - personal_info = f"bei über {anzahl_ma} Mitarbeitern" - except (ValueError, TypeError): + elif int(anzahl_ma or 0) > 0: + personal_info = f"bei über {round_number(int(anzahl_ma))} Mitarbeitern" + except Exception: logger.debug("Keine validen MA/Techniker-Zahlen für Pitch.") - - # 4. Der finale, verbesserte Prompt + + # 4. Prompt bauen prompt_parts = [ - "Du bist ein B2B-Stratege und Texter, der die operativen Herausforderungen eines Unternehmens im technischen Außendienst erkennt und präzise auf den Punkt bringt.", - "Aufgabe: Formuliere EINEN EINZIGEN, flüssig lesbaren Satz (ca. 20-35 Wörter), der eine **hochspezifische operative Service-Herausforderung** des Unternehmens beschreibt.", - - "\n--- Stil-Regeln ---", - "- Formuliere als scharfsinnige Beobachtung, nicht werblich.", - "- Verwende den 'Kurznamen des Unternehmens' natürlich im Satz, ohne Anführungszeichen.", - "- Vermeide generische Phrasen wie 'Schlüssel zum Erfolg'. Fokussiere auf operative Konsequenzen (z.B. 'entscheidend für die Servicequalität', 'unerlässlich für die Anlagenverfügbarkeit').", - "- ABSOLUT KRITISCH: Wenn die Beschreibung zu allgemein ist (z.B. nur 'Dienstleistungen' oder 'Produkte' erwähnt), um eine **spezifische Tätigkeit** (wie 'Wartung von Kompressoren' oder 'Installation von Sicherheitssystemen') abzuleiten, antworte NUR mit dem Wort 'FEHLER_DATEN'.", - - "\n--- Denkprozess (Schritt für Schritt) ---", - "1. Analysiere die Unternehmensdaten. Finde die **spezifischste Produktkategorie oder Dienstleistung**.", - "2. Leite daraus die **konkreteste Tätigkeit** von mobilen Teams ab. 'Service' ist zu allgemein. 'Wartung von Röntgengeräten' ist spezifisch.", - "3. **Inferenz-Regel:** Stellt das Unternehmen ein komplexes technisches Produkt her? Dann ist die Service-Tätigkeit die 'Wartung und Reparatur' DIESES PRODUKTS.", - "4. Formuliere einen Satz, der diese spezifische Aufgabe mit der Personalinfo verbindet und die damit verbundene Herausforderung betont.", - "5. Führe eine Selbstkritik durch: Ist der Satz wirklich spezifisch? Wenn nicht, gib 'FEHLER_DATEN' aus.", - - "\n--- Unternehmenskontext ---", - f"Kurzname des Unternehmens: {display_name}", - f"KI-validierte Branche: {ki_branche}", - f"Beschreibung (aus Website & Wikipedia): {final_beschreibung}", - f"Personalinfo für den Satz: {personal_info}", - f"Gesamtmitarbeiterzahl (Kontext): {anzahl_ma}", - - "\n--- Beispiele für den gewünschten Output-Stil ---", - "Beispiel 1 (Spezifischer Dienstleister): Angesichts des beschleunigten Ausbaus der Ladeinfrastruktur bei EnBW ist die reibungslose Koordination der Installationstermine Ihrer mobilen Teams entscheidend für den Projekterfolg.", - "Beispiel 2 (Spezifischer Hersteller): Bei der Herstellung hochpräziser Medizintechnik für Aesculap ist die termingerechte Wartung und Kalibrierung der chirurgischen Instrumente vor Ort in den Kliniken entscheidend für die Patientensicherheit.", - "Beispiel 3 (Allgemeine Beschreibung -> Fehler): FEHLER_DATEN", - - "\n--- Deine Aufgabe ---", - "Führe den Denkprozess durch und gib NUR den finalen, spezifischen Satz aus ODER das Wort 'FEHLER_DATEN'.", + "Du bist ein B2B-Stratege und Texter, der operative Service-Herausforderungen punktgenau beschreibt.", + "Aufgabe: Formuliere EINEN flüssig lesbaren Satz (20–35 Wörter) zur **hochspezifischen** Service-Herausforderung.", + "", + "--- Stil-Regeln ---", + "- Nicht werblich.", + "- Nutze den Kurznamen ohne Anführungszeichen.", + "- Vermeide allgemeine Phrasen wie 'Schlüssel zum Erfolg'.", + "", + "--- Kontext ---", + f"Kurzname: {display_name}", + f"Branche: {ki_branche}", + f"Beschreibung: {combined}", + f"Personalinfo: {personal_info}", + f"Gesamtmitarbeiterzahl: {anzahl_ma}", + "", + "Bei zu allgemeiner Beschreibung → FEHLER_DATEN", ] - prompt = "\n".join(prompt_parts) - + try: fsm_pitch = call_openai_chat(prompt, temperature=0.6, model="gpt-4o") if not fsm_pitch or "FEHLER_DATEN" in fsm_pitch: - logger.warning(f"KI konnte keinen validen FSM-Pitch für {company_name} generieren (Grund: Mangelnde Daten).") + logger.warning(f"KI konnte keinen validen FSM-Pitch für {company_name} generieren.") return "FEHLER (Mangelnde Daten)" return fsm_pitch.strip().replace('"', '') except Exception as e: logger.error(f"Fehler bei der Generierung des FSM-Pitches für {company_name}: {e}") return "FEHLER (API-Fehler)" - @retry_on_failure def serp_website_lookup(company_name): """ @@ -1386,8 +1362,8 @@ def get_website_raw(url, max_length=20000): if content_area: break if not content_area: - content_area = soup.find('body') - if content_area: + content_area = soup.find('body') + if content_area: #Zeile 1390 banner_selectors = ['[id*="cookie"]', '[class*="cookie"]', '[id*="consent"]', '[class*="consent"]', '.cookie-banner', '.consent-banner', '.modal', '#modal', '.popup', '#popup', '[role="dialog"]', '[aria-modal="true"]'] banners_removed_count = 0 for selector in banner_selectors: