helpers.py aktualisiert
This commit is contained in:
172
helpers.py
172
helpers.py
@@ -1001,161 +1001,137 @@ def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kateg
|
|||||||
|
|
||||||
@retry_on_failure
|
@retry_on_failure
|
||||||
def verify_wiki_article_chatgpt(company_name, website, wiki_url):
|
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 = 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:**
|
prompt = """Aufgabe: Bewerte als erfahrener Datenanalyst, ob der Wikipedia-Artikel die relevanteste verfügbare Seite für das angegebene Unternehmen ist.
|
||||||
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.
|
|
||||||
|
|
||||||
**Unternehmen:**
|
Deine Bewertungsregeln:
|
||||||
- Name: {company_name}
|
1. Muttergesellschaft ist OK: Der Artikel darf auch die Muttergesellschaft beschreiben (z.B. für "ABB Automation GmbH" ist der Artikel "ABB (Unternehmen)" korrekt).
|
||||||
- Website: {website}
|
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:**
|
Unternehmen:
|
||||||
- URL: {wiki_url}
|
Name: {company_name}
|
||||||
|
Website: {website}
|
||||||
|
|
||||||
**Deine Antwort (antworte ausschließlich in diesem Format):**
|
Vorgegebener Wikipedia-Artikel:
|
||||||
Konsistenz: <OK, wenn der Artikel nach den oben genannten Regeln relevant ist, sonst X>
|
Artikel: {wiki_url}
|
||||||
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.>
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
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:
|
try:
|
||||||
chat_response = call_openai_chat(prompt, temperature=0.0)
|
chat_response = call_openai_chat(prompt, temperature=0.0)
|
||||||
if not chat_response:
|
if not chat_response:
|
||||||
raise APIError("Keine Antwort von OpenAI für Wiki-Verifizierung erhalten.")
|
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 = {}
|
# Robustes Parsing
|
||||||
for line in lines:
|
parsed = {"consistency": "FEHLER PARSING", "justification": "", "suggested_url": ""}
|
||||||
|
for line in chat_response.strip().splitlines():
|
||||||
if ":" in line:
|
if ":" in line:
|
||||||
key, value = line.split(":", 1)
|
key, val = line.split(":", 1)
|
||||||
key = key.strip().lower().replace(" ", "_")
|
key = key.strip().lower().replace(" ", "_")
|
||||||
value = value.strip()
|
parsed[key] = val.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 {
|
||||||
return result
|
"consistency": parsed.get("konsistenz", "FEHLER PARSING").upper(),
|
||||||
|
"justification": parsed.get("begründung", ""),
|
||||||
|
"suggested_url": parsed.get("vorschlag_url", ""),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Fehler bei der ChatGPT Wiki-Verifizierung: {e}")
|
logger.error(f"Fehler bei der ChatGPT Wiki-Verifizierung: {e}")
|
||||||
return {"consistency": "FEHLER API", "justification": str(e), "suggested_url": ""}
|
return {"consistency": "FEHLER API", "justification": str(e), "suggested_url": ""}
|
||||||
|
|
||||||
# ==============================================================================
|
|
||||||
# Chat GPT FSM Pitch
|
|
||||||
# ==============================================================================
|
|
||||||
|
|
||||||
|
|
||||||
@retry_on_failure
|
@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
|
Generiert einen maßgeschneiderten, nicht-werblichen Satz, der eine operative
|
||||||
Service-Herausforderung des Unternehmens beschreibt (v2.1).
|
Service-Herausforderung des Unternehmens beschreibt (v2.1).
|
||||||
"""
|
"""
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# 1. Daten-Check: Genug Futter für einen guten Satz?
|
# 1. Daten-Check
|
||||||
beschreibung_kombiniert = []
|
parts = []
|
||||||
if website_summary and 'k.a.' not in website_summary.lower():
|
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():
|
if wiki_absatz and 'k.a.' not in wiki_absatz.lower():
|
||||||
beschreibung_kombiniert.append(f"Wikipedia-Einleitung: {wiki_absatz}")
|
parts.append(f"Wikipedia-Einleitung: {wiki_absatz}")
|
||||||
|
combined = "\n".join(parts)
|
||||||
|
|
||||||
final_beschreibung = "\n".join(beschreibung_kombiniert)
|
if len(combined.split()) < 10:
|
||||||
|
logger.warning(f"Zu wenige Informationen für FSM-Pitch bei {company_name}.")
|
||||||
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.")
|
|
||||||
return "FEHLER (Mangelnde Daten)"
|
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
|
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):
|
def round_number(n):
|
||||||
if n < 100: return n
|
if n < 100: return n
|
||||||
if n < 1000: return int(round(n / 50.0)) * 50
|
if n < 1000: return int(round(n / 50.0)) * 50
|
||||||
return int(round(n / 100.0)) * 100
|
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:
|
try:
|
||||||
# Priorität 1: Explizite Technikerzahl
|
tech = int(anzahl_techniker or 0)
|
||||||
if anzahl_techniker and int(anzahl_techniker) > 0:
|
if tech > 0:
|
||||||
anzahl_techniker = round_number(int(anzahl_techniker))
|
personal_info = f"bei rund {round_number(tech)} Servicetechnikern"
|
||||||
personal_info = f"bei rund {anzahl_techniker} Servicetechnikern"
|
elif techniker_bucket_ml and 'k.a.' not in techniker_bucket_ml.lower():
|
||||||
# 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:
|
|
||||||
personal_info = f"bei schätzungsweise {techniker_bucket_ml} Servicetechnikern"
|
personal_info = f"bei schätzungsweise {techniker_bucket_ml} Servicetechnikern"
|
||||||
# Priorität 3: Gesamtmitarbeiterzahl
|
elif int(anzahl_ma or 0) > 0:
|
||||||
elif anzahl_ma and int(anzahl_ma) > 0:
|
personal_info = f"bei über {round_number(int(anzahl_ma))} Mitarbeitern"
|
||||||
anzahl_ma = round_number(int(anzahl_ma))
|
except Exception:
|
||||||
personal_info = f"bei über {anzahl_ma} Mitarbeitern"
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
logger.debug("Keine validen MA/Techniker-Zahlen für Pitch.")
|
logger.debug("Keine validen MA/Techniker-Zahlen für Pitch.")
|
||||||
|
|
||||||
# 4. Der finale, verbesserte Prompt
|
# 4. Prompt bauen
|
||||||
prompt_parts = [
|
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.",
|
"Du bist ein B2B-Stratege und Texter, der operative Service-Herausforderungen punktgenau beschreibt.",
|
||||||
"Aufgabe: Formuliere EINEN EINZIGEN, flüssig lesbaren Satz (ca. 20-35 Wörter), der eine **hochspezifische operative Service-Herausforderung** des Unternehmens beschreibt.",
|
"Aufgabe: Formuliere EINEN flüssig lesbaren Satz (20–35 Wörter) zur **hochspezifischen** Service-Herausforderung.",
|
||||||
|
"",
|
||||||
"\n--- Stil-Regeln ---",
|
"--- Stil-Regeln ---",
|
||||||
"- Formuliere als scharfsinnige Beobachtung, nicht werblich.",
|
"- Nicht werblich.",
|
||||||
"- Verwende den 'Kurznamen des Unternehmens' natürlich im Satz, ohne Anführungszeichen.",
|
"- Nutze den Kurznamen 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').",
|
"- Vermeide allgemeine Phrasen wie 'Schlüssel zum Erfolg'.",
|
||||||
"- 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'.",
|
"",
|
||||||
|
"--- Kontext ---",
|
||||||
"\n--- Denkprozess (Schritt für Schritt) ---",
|
f"Kurzname: {display_name}",
|
||||||
"1. Analysiere die Unternehmensdaten. Finde die **spezifischste Produktkategorie oder Dienstleistung**.",
|
f"Branche: {ki_branche}",
|
||||||
"2. Leite daraus die **konkreteste Tätigkeit** von mobilen Teams ab. 'Service' ist zu allgemein. 'Wartung von Röntgengeräten' ist spezifisch.",
|
f"Beschreibung: {combined}",
|
||||||
"3. **Inferenz-Regel:** Stellt das Unternehmen ein komplexes technisches Produkt her? Dann ist die Service-Tätigkeit die 'Wartung und Reparatur' DIESES PRODUKTS.",
|
f"Personalinfo: {personal_info}",
|
||||||
"4. Formuliere einen Satz, der diese spezifische Aufgabe mit der Personalinfo verbindet und die damit verbundene Herausforderung betont.",
|
f"Gesamtmitarbeiterzahl: {anzahl_ma}",
|
||||||
"5. Führe eine Selbstkritik durch: Ist der Satz wirklich spezifisch? Wenn nicht, gib 'FEHLER_DATEN' aus.",
|
"",
|
||||||
|
"Bei zu allgemeiner Beschreibung → FEHLER_DATEN",
|
||||||
"\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'.",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
prompt = "\n".join(prompt_parts)
|
prompt = "\n".join(prompt_parts)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
fsm_pitch = call_openai_chat(prompt, temperature=0.6, model="gpt-4o")
|
fsm_pitch = call_openai_chat(prompt, temperature=0.6, model="gpt-4o")
|
||||||
if not fsm_pitch or "FEHLER_DATEN" in fsm_pitch:
|
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 "FEHLER (Mangelnde Daten)"
|
||||||
return fsm_pitch.strip().replace('"', '')
|
return fsm_pitch.strip().replace('"', '')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Fehler bei der Generierung des FSM-Pitches für {company_name}: {e}")
|
logger.error(f"Fehler bei der Generierung des FSM-Pitches für {company_name}: {e}")
|
||||||
return "FEHLER (API-Fehler)"
|
return "FEHLER (API-Fehler)"
|
||||||
|
|
||||||
|
|
||||||
@retry_on_failure
|
@retry_on_failure
|
||||||
def serp_website_lookup(company_name):
|
def serp_website_lookup(company_name):
|
||||||
"""
|
"""
|
||||||
@@ -1386,8 +1362,8 @@ def get_website_raw(url, max_length=20000):
|
|||||||
if content_area: break
|
if content_area: break
|
||||||
|
|
||||||
if not content_area:
|
if not content_area:
|
||||||
content_area = soup.find('body')
|
content_area = soup.find('body')
|
||||||
if content_area:
|
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"]']
|
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
|
banners_removed_count = 0
|
||||||
for selector in banner_selectors:
|
for selector in banner_selectors:
|
||||||
|
|||||||
Reference in New Issue
Block a user