helpers.py aktualisiert
This commit is contained in:
188
helpers.py
188
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: <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 (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:
|
||||
|
||||
Reference in New Issue
Block a user