helpers.py aktualisiert

This commit is contained in:
2025-07-18 17:56:30 +00:00
parent 43855e3737
commit 5298b97ed7

View File

@@ -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: <OK, wenn der Artikel nach den oben genannten Regeln relevant ist, sonst X>
Begründung: <Sehr kurze Begründung für deine Entscheidung. Erwähne, welche Regel zutraf (z.B. "Muttergesellschaft passt" oder "Name ohne Rechtsform passt").>
Vorschlag URL: <Gib hier eine bessere URL an, falls du eine findest. Sonst leer lassen.>
"""
Vorgegebener Wikipedia-Artikel:
Artikel: {wiki_url}
Antwortformat:
Konsistenz: <OK, wenn relevant, sonst X>
Begründung: <Sehr kurze Begründung. Welche Regel traf zu?>
Vorschlag URL: <Bessere URL oder leer>
""".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 (2035 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: