This commit is contained in:
2025-05-07 11:25:11 +00:00
parent df43639a21
commit 0811337c9e

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3
"""
Automatisiertes Unternehmensbewertungs-Skript - Refactoring v1.7.1
Automatisiertes Unternehmensbewertungs-Skript - Refactoring v1.7.0
Basierend auf v1.6.x - Umstrukturierung in modulare Klassen und flexibles UI.
Dieses Skript dient der automatisierten Anreicherung, Validierung und Standardisierung
@@ -8,12 +8,14 @@ von Unternehmensdaten, primär aus einem Google Sheet, ergänzt durch Web Scrapi
Wikipedia, OpenAI (ChatGPT) und SerpAPI (Google Search, LinkedIn).
Autor: [Ihr Name/Pseudonym]
Version: v1.7.1
Version: v1.7.0
Hinweis zur Struktur:
Dieser Code wird in 4 logischen Bloecken uebermittelt. Fuegen Sie die Bloecke
Dieser Code wird in logischen Bloecken uebermittelt. Fuegen Sie die Bloecke
nacheinander in diese einzige Datei ein, achten Sie sorgfaeltig auf die
Einrueckung. Jeder Block muss auf oberster Ebene eingefuegt werden (keine Einrueckung).
Die Kommentare wie '# =================================================='
markieren den Beginn neuer logischer Sektionen oder Klassen.
"""
# ==============================================================================
@@ -28,7 +30,7 @@ import json
import pickle
import threading
import traceback
import logging
import logging # logging Modul importieren
import argparse
import random # Fuer Jitter im Retry Decorator
from datetime import datetime
@@ -36,7 +38,8 @@ from urllib.parse import urlparse, urlencode, unquote
# Externe Bibliotheken
import gspread
# Stellen Sie sicher, dass gspread >= 5.0.0 installiert ist.
# Stellen Sie sicher, dass gspread >= 5.0.0 installiert ist, da APIError anders behandelt wird
# (Unser Code sollte mit den neueren Versionen kompatibel sein)
import wikipedia
# Stellen Sie sicher, dass wikipedia-api nicht gleichzeitig installiert ist (Konflikt).
import requests # Fuer HTTP-Anfragen
@@ -137,17 +140,14 @@ class Config:
@classmethod
def load_api_keys(cls):
"""Laedt API-Schluessel aus den definierten Dateien."""
# Der Logger ist hier noch nicht vollstaendig konfiguriert, verwenden Sie print
# logging.info wird nach Konfiguration des File Handlers korrekt funktionieren
# Verwenden Sie print, da der Logger hier noch nicht vollstaendig konfiguriert ist.
print("Lade API-Schluessel...")
cls.API_KEYS['openai'] = cls._load_key_from_file(API_KEY_FILE)
cls.API_KEYS['serpapi'] = cls._load_key_from_file(SERP_API_KEY_FILE)
cls.API_KEYS['genderize'] = cls._load_key_from_file(GENDERIZE_API_KEY_FILE)
# Stelle sicher, dass das 'openai' Modul im Scope dieser Methode bekannt ist
# Dies sollte eigentlich nicht noetig sein, wenn es global importiert wurde,
# aber als explizite Massnahme:
import openai # <<< FÜGEN SIE DIESE IMPORT-ZEILE HIER HINZU
import openai # Importiere hier, um sicherzustellen, dass es verfuegbar ist
if cls.API_KEYS.get('openai'):
# Setze den OpenAI API Key global fuer die Bibliothek
@@ -170,90 +170,30 @@ class Config:
with open(filepath, "r", encoding="utf-8") as f:
key = f.read().strip()
if key:
# print(f"Schluessel aus '{filepath}' erfolgreich geladen.") # Zu viel Laerm im Debug
return key
else:
# Logge auf Warning, wenn die Datei leer ist
print(f"WARNUNG: Datei '{filepath}' ist leer.")
return None
except FileNotFoundError:
# Logge auf Info, da das Fehlen eines Keys nicht immer ein Fehler sein muss
print(f"INFO: API-Schluesseldatei '{filepath}' nicht gefunden.")
return None
except Exception as e:
# Logge auf Error, wenn beim Lesen ein anderer Fehler auftritt
print(f"FEHLER beim Lesen der Schluesseldatei '{filepath}': {e}")
return None
# --- Globale Spalten-Mapping (WICHTIG: MUSS ZU IHREM SHEET PASSEN!) ---
# Index ist 0-basiert, Spaltenbuchstaben sind 1-basiert (A=1, AW=49)
# Dies sollte die Mapping-Definition aus Ihrem Code (Teil 1) sein, vervollstaendigt.
# UEBERPRUEFEN SIE DIESES MAPPING SORGFÄLTIG GEGEN IHR SHEET!
COLUMN_MAP = {
"ReEval Flag": 0, # A - Markierungsspalte fuer manuelle Re-Evaluation
"CRM Name": 1, # B - Unternehmensname aus CRM
"CRM Kurzform": 2, # C - Manuell gepflegte Kurzform
"CRM Website": 3, # D - Website URL aus CRM (kann durch Skript ergaenzt werden)
"CRM Ort": 4, # E - Ort aus CRM
"CRM Beschreibung": 5, # F - Beschreibung aus CRM
"CRM Branche": 6, # G - Branche aus CRM
"CRM Beschreibung Branche extern": 7, # H - Externe Branchenbeschreibung (falls vorhanden)
"CRM Anzahl Techniker": 8, # I - Bekannte Anzahl Servicetechniker aus CRM (Zielvariable fuer ML)
"CRM Umsatz": 9, # J - Umsatz aus CRM
"CRM Anzahl Mitarbeiter": 10, # K - Anzahl Mitarbeiter aus CRM
"CRM Vorschlag Wiki URL": 11, # L - Vorschlag fuer Wiki URL (kann manuell gepflegt werden)
"Wiki URL": 12, # M - Gefundene oder validierte Wikipedia URL
"Wiki Absatz": 13, # N - Erster Absatz des Wikipedia Artikels
"Wiki Branche": 14, # O - Branche aus Wikipedia Infobox
"Wiki Umsatz": 15, # P - Umsatz aus Wikipedia Infobox
"Wiki Mitarbeiter": 16, # Q - Mitarbeiterzahl aus Wikipedia Infobox
"Wiki Kategorien": 17, # R - Wikipedia Kategorien
"Chat Wiki Konsistenzpruefung": 18, # S - ChatGPT Check: Passt Wiki Artikel zum Unternehmen? ('OK', 'X', '?')
"Chat Begruendung Wiki Inkonsistenz": 19, # T - Begruendung, wenn S='X'
"Chat Vorschlag Wiki Artikel": 20, # U - ChatGPT Vorschlag fuer alternativen Wiki Artikel (falls S='X')
"Begruendung bei Abweichung": 21, # V - Nicht mehr primaer genutzt (Begruendung CRM vs Wiki URL)
"Chat Vorschlag Branche": 22, # W - ChatGPT Vorschlag fuer Branche (Zielschema)
"Chat Konsistenz Branche": 23, # X - Vergleich W vs. G ('ok', 'X', 'fallback_...')
"Chat Begruendung Abweichung Branche": 24, # Y - Begruendung fuer W
"Chat Pruefung FSM Relevanz": 25, # Z - ChatGPT Check: Ist das Unternehmen fuer FSM relevant?
"Chat Begruendung fuer FSM Relevanz": 26, # AA - Begruendung fuer Z
"Chat Schaetzung Anzahl Mitarbeiter": 27, # AB - ChatGPT Schaetzung Mitarbeiter
"Chat Konsistenzpruefung Mitarbeiterzahl": 28, # AC - Vergleich AB vs. K/Q
"Chat Begruendung Abweichung Mitarbeiterzahl": 29, # AD - Begruendung fuer AB/AC
"Chat Einschaetzung Anzahl Servicetechniker": 30, # AE - ChatGPT Schaetzung Servicetechniker
"Chat Begruendung Abweichung Anzahl Servicetechniker": 31, # AF - Begruendung fuer AE
"Chat Schaetzung Umsatz": 32, # AG - ChatGPT Schaetzung Umsatz
"Chat Begruendung Abweichung Umsatz": 33, # AH - Begruendung fuer AG
"Linked Serviceleiter gefunden": 34, # AI - Anzahl gefundener Kontakte (Serviceleiter)
"Linked It-Leiter gefunden": 35, # AJ - Anzahl gefundener Kontakte (IT-Leiter)
"Linked Management gefunden": 36, # AK - Anzahl gefundener Kontakte (Management)
"Linked Disponent gefunden": 37, # AL - Anzahl gefundener Kontakte (Disponent)
"Contact Search Timestamp": 38, # AM - Timestamp der letzten LinkedIn Suche
"Wikipedia Timestamp": 39, # AN - Timestamp der letzten erfolgreichen Wiki Extraktion (M-R befuellt)
"Timestamp letzte Pruefung": 40, # AO - Timestamp der letzten ChatGPT Evaluationen (W-Y, Z-AD, AE-AH, AG-AH befuellt)
"Version": 41, # AP - Skriptversion, die die Zeile zuletzt bearbeitet hat
"Tokens": 42, # AQ - Anzahl Tokens des letzten OpenAI Calls fuer diese Zeile (ggf. aggregiert)
"Website Rohtext": 43, # AR - Roh extrahierter Text von der Website
"Website Zusammenfassung": 44, # AS - ChatGPT Zusammenfassung von AR
"Website Scrape Timestamp": 45, # AT - Timestamp des letzten erfolgreichen Website Scrapings (AR, AS befuellt)
"Geschaetzter Techniker Bucket": 46, # AU - Ergebnis des ML-Modells (Bucket)
"Finaler Umsatz (Wiki>CRM)": 47,# AV - Konsolidierter Umsatz (Wiki > CRM)
"Finaler Mitarbeiter (Wiki>CRM)": 48, # AW - Konsolidierte Mitarbeiterzahl (Wiki > CRM)
"Wiki Verif. Timestamp": 49, # AX - Timestamp der letzten Wiki-Verifikation (S-U befuellt)
"SerpAPI Wiki Search Timestamp": 50 # AY - Timestamp der letzten SerpAPI-Suche nach fehlender Wiki-URL (Modus find_wiki_serp)
"ReEval Flag": 0, "CRM Name": 1, "CRM Kurzform": 2, "CRM Website": 3, "CRM Ort": 4, "CRM Beschreibung": 5, "CRM Branche": 6, "CRM Beschreibung Branche extern": 7, "CRM Anzahl Techniker": 8, "CRM Umsatz": 9, "CRM Anzahl Mitarbeiter": 10, "CRM Vorschlag Wiki URL": 11, "Wiki URL": 12, "Wiki Absatz": 13, "Wiki Branche": 14, "Wiki Umsatz": 15, "Wiki Mitarbeiter": 16, "Wiki Kategorien": 17, "Chat Wiki Konsistenzpruefung": 18, "Chat Begruendung Wiki Inkonsistenz": 19, "Chat Vorschlag Wiki Artikel": 20, "Begruendung bei Abweichung": 21, "Chat Vorschlag Branche": 22, "Chat Konsistenz Branche": 23, "Chat Begruendung Abweichung Branche": 24, "Chat Pruefung FSM Relevanz": 25, "Chat Begruendung fuer FSM Relevanz": 26, "Chat Schaetzung Anzahl Mitarbeiter": 27, "Chat Konsistenzpruefung Mitarbeiterzahl": 28, "Chat Begruendung Abweichung Mitarbeiterzahl": 29, "Chat Einschaetzung Anzahl Servicetechniker": 30, "Chat Begruendung Abweichung Anzahl Servicetechniker": 31, "Chat Schaetzung Umsatz": 32, "Chat Begruendung Abweichung Umsatz": 33, "Linked Serviceleiter gefunden": 34, "Linked It-Leiter gefunden": 35, "Linked Management gefunden": 36, "Linked Disponent gefunden": 37, "Contact Search Timestamp": 38, "Wikipedia Timestamp": 39, "Timestamp letzte Pruefung": 40, "Version": 41, "Tokens": 42, "Website Rohtext": 43, "Website Zusammenfassung": 44, "Website Scrape Timestamp": 45, "Geschaetzter Techniker Bucket": 46, "Finaler Umsatz (Wiki>CRM)": 47, "Finaler Mitarbeiter (Wiki>CRM)": 48, "Wiki Verif. Timestamp": 49, "SerpAPI Wiki Search Timestamp": 50
}
# Bestaetigen Sie, dass dies Ihre tatsaechlichen Spalten sind!
# --- Globale Variablen fuer Branch Mapping (werden von load_target_schema() befuellt) ---
# BRANCH_MAPPING wird derzeit nicht verwendet, kann aber beibehalten werden.
BRANCH_MAPPING = {}
TARGET_SCHEMA_STRING = "Ziel-Branchenschema nicht verfuegbar." # String-Repraesentation des Schemas fuer Prompts
ALLOWED_TARGET_BRANCHES = [] # Liste der erlaubten Kurzformen
TARGET_SCHEMA_STRING = "Ziel-Branchenschema nicht verfuegbar."
ALLOWED_TARGET_BRANCHES = []
# ==============================================================================
# Ende Basis-Setup & Globale Helfer Block
# Ende Basis-Setup Block
# ==============================================================================
# ==============================================================================
@@ -374,82 +314,54 @@ def retry_on_failure(func):
# ==============================================================================
# ==============================================================================
# 3. GLOBALE HELPER FUNCTIONS (PART 2: Logging & Token Count)
# (Teil von logisch 'utils.py')
# GLOBALE HELPER FUNCTIONS (PART 2: Logging & Token Count)
# ==============================================================================
# --- Token Count Funktion ---
# Zaehlt Tokens via tiktoken oder schaetzt ueber Leerzeichen.
# Der retry_on_failure Decorator ist hier nicht sinnvoll, da es eine lokale Berechnung ist.
def token_count(text, model=None):
"""Zaehlt Tokens via tiktoken oder schaetzt ueber Leerzeichen."""
# Verwenden Sie logger, da das Logging jetzt konfiguriert ist (im Root Logger)
logger = logging.getLogger(__name__) # Logger-Instanz holen
if not text or not isinstance(text, str): return 0
# ... (Rest der token_count Funktion wie in Block 3/36 gesendet) ...
current_model = model if model else getattr(Config, 'TOKEN_MODEL', 'gpt-3.5-turbo')
if tiktoken:
try:
# Cache encoding object per model
if not hasattr(token_count, 'enc_cache'):
token_count.enc_cache = {}
if current_model not in token_count.enc_cache:
token_count.enc_cache[current_model] = tiktoken.encoding_for_model(current_model)
if not hasattr(token_count, 'enc_cache'): token_count.enc_cache = {}
if current_model not in token_count.enc_cache: token_count.enc_cache[current_model] = tiktoken.encoding_for_model(current_model)
enc = token_count.enc_cache[current_model]
return len(enc.encode(text))
except Exception as e:
# Logge Fehler auf Debug, da dies ein Fallback ist
logger.debug(f"Fehler beim Token-Counting mit tiktoken fuer Modell '{current_model}': {e} - Fallback zur Schaetzung.")
# Fallback zur Schaetzung
return len(str(text).split()) # Sicherstellen, dass text ein String ist
return len(str(text).split())
else:
# Fallback Schaetzung
return len(str(text).split()) # Sicherstellen, dass text ein String ist
return len(str(text).split())
# --- Logging Helpers ---
# Erstellt den Logdateinamen.
# LOG_FILE ist global definiert und wird in main() gesetzt (Block 34)
LOG_FILE = None # Initialisierung, falls noch nicht geschehen
LOG_FILE = None # Initialisierung
def create_log_filename(mode):
"""Erstellt einen zeitgestempelten Logdateinamen im LOG_DIR."""
# Verwenden Sie logger, da das Logging jetzt konfiguriert ist (print am Anfang von main)
log_dir_path = LOG_DIR # Nutzt die globale Konstante (Block 1)
# Erstelle das Log-Verzeichnis, falls es nicht existiert
logger = logging.getLogger(__name__) # Logger-Instanz holen
log_dir_path = LOG_DIR
# ... (Rest der create_log_filename Funktion wie in Block 3/36 gesendet) ...
if not os.path.exists(log_dir_path):
try:
# exist_ok=True verhindert Fehler, wenn das Verzeichnis bereits existiert
os.makedirs(log_dir_path, exist_ok=True)
# Logge die Erstellung des Verzeichnisses
logger.info(f"Log-Verzeichnis '{log_dir_path}' erstellt.")
except Exception as e:
# Logge Fehler auf Error-Level
logger.error(f"FEHLER: Konnte Log-Verzeichnis '{log_dir_path}' nicht erstellen: {e}")
# Versuche, die Datei im aktuellen Verzeichnis zu erstellen, wenn LOG_DIR fehlschlaegt
log_dir_path = "." # Fallback Verzeichnis
log_dir_path = "."
logger.warning(f"Versuche, Logdatei im aktuellen Verzeichnis '{log_dir_path}' zu erstellen.")
# Erstelle den Dateinamen mit Zeitstempel und Version
try:
now = datetime.now().strftime("%d-%m-%Y_%H-%M")
# Sicherstellen, dass Config.VERSION verfuegbar ist (Block 1), Fallback falls nicht
ver_short = getattr(Config, 'VERSION', 'unknown').replace(".", "")
filename = f"{now}_{ver_short}_Modus{mode}.txt"
# Gebe den vollstaendigen Pfad zurueck
return os.path.join(log_dir_path, filename)
except Exception as e_fallback:
# Logge Fehler bei der Dateinamen-Erstellung
logger.error(f"FEHLER: Konnte Logdateinamen auch im Fallback-Verzeichnis '{log_dir_path}' nicht erstellen: {e_fallback}")
# Signalisiere Fehler durch Rueckgabe von None
return None
# debug_print ist nicht mehr notwendig, da wir das Standard-Logging nutzen.
# Alle bisherigen Aufrufe von debug_print werden durch logger.debug, logger.info, logger.warning, logger.error, logger.critical ersetzt.
# ==============================================================================
# Ende Grundlegende Helfer Block
# ==============================================================================
@@ -464,6 +376,7 @@ def create_log_filename(mode):
def simple_normalize_url(url):
"""Normalisiert URL zu domain.tld oder k.A. (ohne www, ohne Pfad)."""
# Verwenden Sie logger, da das Logging jetzt konfiguriert ist
logger = logging.getLogger(__name__) # <<< DIESE ZEILE HINZUFÜGEN
if not url or not isinstance(url, str): return "k.A."
url = url.strip()
# Pruefe auf Kleinbuchstaben "k.A." und leere Strings nach dem Strippen
@@ -531,6 +444,7 @@ def simple_normalize_url(url):
def normalize_string(s):
"""Normalisiert Umlaute und Sonderzeichen nach einer definierten Liste."""
# Verwenden Sie logger, da das Logging jetzt konfiguriert ist
logger = logging.getLogger(__name__) # <<< DIESE ZEILE HINZUFÜGEN
if not s or not isinstance(s, str): return ""
# Ersetzungen fuer gaengige deutsche Umlaute und Sonderzeichen
replacements = { 'Ä': 'Ae', 'Ö': 'Oe', 'Ü': 'Ue', 'ß': 'ss', 'ä': 'ae', 'ö': 'oe', 'ü': 'ue',
@@ -565,6 +479,7 @@ def clean_text(text):
Entfernt gaengige unerwuenschte Muster wie [1], [Bearbeiten].
"""
# Verwenden Sie logger, da das Logging jetzt konfiguriert ist
logger = logging.getLogger(__name__) # <<< DIESE ZEILE HINZUFÜGEN
if text is None: return "k.A." # Behandle None explizit
try:
text = str(text) # Sicherstellen, dass es ein String ist
@@ -684,6 +599,7 @@ def extract_numeric_value(raw_value, is_umsatz=False):
und gaengige Praefixe/Suffixe. Gibt "k.A." zurueck, wenn nicht extrahierbar oder <= 0.
"""
# Verwenden Sie logger, da das Logging jetzt konfiguriert ist
logger = logging.getLogger(__name__) # <<< DIESE ZEILE HINZUFÜGEN
if not raw_value: return "k.A."
raw_value_str = str(raw_value).strip()
# Pruefe auf bekannte "keine Angabe" Strings oder 0 als Text
@@ -776,6 +692,7 @@ def get_numeric_filter_value(value_str, is_umsatz=False):
Beachtet Einheiten (Tsd, Mio, Mrd) fuer Umsatz.
"""
# Verwenden Sie logger, da das Logging jetzt konfiguriert ist
logger = logging.getLogger(__name__) # <<< DIESE ZEILE HINZUFÜGEN
if value_str is None or pd.isna(value_str) or str(value_str).strip() == '':
# Gibt 0 (int/float) zurueck, nicht "k.A." fuer Filterlogik
return 0.0 if is_umsatz else 0
@@ -891,6 +808,7 @@ except Exception as e:
def get_gender(firstname):
"""Ermittelt Geschlecht via gender-guesser und Fallback Genderize API."""
# Verwenden Sie logger, da das Logging jetzt konfiguriert ist
logger = logging.getLogger(__name__) # <<< DIESE ZEILE HINZUFÜGEN
if not firstname or not isinstance(firstname, str): return "unknown"
# Nehmen Sie nur den ersten Teil des Vornamens und bereinigen Sie ihn
firstname_clean = str(firstname).strip().split(" ")[0]
@@ -970,6 +888,7 @@ def get_email_address(firstname, lastname, website):
Normalisiert Namen und extrahiert die Domain aus der Website-URL.
"""
# Verwenden Sie logger, da das Logging jetzt konfiguriert ist
logger = logging.getLogger(__name__) # <<< DIESE ZEILE HINZUFÜGEN
if not all([firstname, lastname, website]) or not all(isinstance(x, str) and x.strip() for x in [firstname, lastname, website]): # Pruefen Sie auf nicht-leere Strings
logger.debug("get_email_address skipped: Fehlende oder ungueltige Eingabe (Name, Website).")
return "" # Gebe leeren String bei fehlenden/ungueltigen Eingaben zurueck
@@ -1040,7 +959,7 @@ def load_target_schema(csv_filepath=BRANCH_MAPPING_FILE):
csv_filepath (str, optional): Pfad zur CSV-Datei mit dem Branchenschema.
Defaults to the global BRANCH_MAPPING_FILE.
"""
# Verwenden Sie logger, da das Logging jetzt konfiguriert ist
logger = logging.getLogger(__name__) # <<< DIESE ZEILE HINZUFÜGEN
# Zugriff auf die globalen Variablen
global BRANCH_MAPPING, TARGET_SCHEMA_STRING, ALLOWED_TARGET_BRANCHES
@@ -1050,7 +969,7 @@ def load_target_schema(csv_filepath=BRANCH_MAPPING_FILE):
allowed_branches_set = set() # Nutzt ein Set, um Duplikate automatisch zu behandeln
line_count = 0
logger.info(f"Lade Ziel-Schema (Kurzformen) aus '{csv_filepath}' Spalte A...")
logger.info(f"Lade Ziel-Schema (Kurzformen) aus '{csv_filepath}' Spalte A...") # Jetzt sollte logger definiert sein
try:
# Versuche, die Datei mit UTF-8-BOM-Signatur oder normalem UTF-8 zu oeffnen
@@ -1153,6 +1072,7 @@ def call_openai_chat(prompt, temperature=0.3, model=None):
Wirft Exception bei API-Fehlern nach Retries.
"""
# Verwenden Sie logger, da das Logging jetzt konfiguriert ist
logger = logging.getLogger(__name__) # <<< DIESE ZEILE HINZUFÜGEN
# Pruefen Sie, ob der API Key konfiguriert ist
if not Config.API_KEYS.get('openai'):
logger.error("Fehler: OpenAI API Key nicht konfiguriert.")
@@ -1260,6 +1180,7 @@ def summarize_website_content(raw_text):
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
# Pruefe, ob gueltiger Rohtext vorhanden ist (nicht leer oder Standard-Fehlerwerte)
if not raw_text or str(raw_text).strip() == "" or str(raw_text).strip().lower() in ["k.a.", "k.a. (nur cookie-banner erkannt)", "k.a. (fehler)"]:
logger.debug("summarize_website_content skipped: No valid raw text provided.")
@@ -1326,6 +1247,7 @@ def summarize_batch_openai(tasks_data):
Wirft Exception bei endgueltigen API-Fehlern nach Retries.
"""
# Verwenden Sie logger, da das Logging jetzt konfiguriert ist
logger = logging.getLogger(__name__) # <<< DIESE ZEILE HINZUFÜGEN
if not tasks_data: return {} # Gebe leeres Dictionary zurueck, wenn keine Tasks da sind
# Filtere Tasks, die gueltigen Text haben (nicht leer oder Standard-Fehlerwerte).
@@ -1481,6 +1403,7 @@ def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kateg
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
@@ -1690,6 +1613,7 @@ def serp_wikipedia_lookup(company_name, website=None, min_score=0.4):
Wirft Exception bei API-Fehlern nach Retries.
"""
# Verwenden Sie logger, da das Logging jetzt konfiguriert ist
logger = logging.getLogger(__name__) # <<< DIESE ZEILE HINZUFÜGEN
serp_key = Config.API_KEYS.get('serpapi')
if not serp_key:
logger.error("Fehler: SerpAPI Key nicht verfuegbar fuer Wikipedia Lookup.")
@@ -1842,6 +1766,7 @@ def serp_website_lookup(company_name):
Wirft Exception bei API-Fehlern nach Retries.
"""
# Verwenden Sie logger, da das Logging jetzt konfiguriert ist
logger = logging.getLogger(__name__) # <<< DIESE ZEILE HINZUFÜGEN
serp_key = Config.API_KEYS.get('serpapi')
if not serp_key:
logger.error("Fehler: SerpAPI Key nicht verfuegbar fuer Website Lookup.")
@@ -1974,6 +1899,7 @@ def search_linkedin_contacts(company_name, website, position_query, crm_kurzform
Wirft Exception bei API-Fehlern nach Retries.
"""
# Verwenden Sie logger, da das Logging jetzt konfiguriert ist
logger = logging.getLogger(__name__) # <<< DIESE ZEILE HINZUFÜGEN
serp_key = Config.API_KEYS.get('serpapi')
if not serp_key:
logger.error("Fehler: SerpAPI Key nicht verfuegbar fuer LinkedIn Suche.")
@@ -2154,6 +2080,7 @@ def get_website_raw(url, max_length=20000, verify_cert=True): # Längeres Defaul
Gibt den Rohtext zurück oder einen Fehlerwert ("k.A.", "k.A. (Fehler)", etc.).
"""
# Verwenden Sie logger, da das Logging jetzt konfiguriert ist
logger = logging.getLogger(__name__) # <<< DIESE ZEILE HINZUFÜGEN
# Pruefen Sie auf ungueltige oder leere URLs, inklusive spezifischer Fehlerwerte wie "http:"
if not url or not isinstance(url, str) or url.strip().lower() in ["k.a.", "kein artikel gefunden", "fehler bei suche", "http:"]:
logger.debug(f"get_website_raw skipped: Ungueltige oder leere URL '{url}'.")
@@ -2320,6 +2247,7 @@ def scrape_website_details(url):
str: Extrahierte Details als String oder Fehler/k.A.
"""
# Verwenden Sie logger, da das Logging jetzt konfiguriert ist
logger = logging.getLogger(__name__) # <<< DIESE ZEILE HINZUFÜGEN
# Pruefen Sie auf ungueltige oder leere URLs
if not url or not isinstance(url, str) or url.strip().lower() in ["k.a.", "kein artikel gefunden", "fehler bei suche", "http:"]:
logger.debug(f"scrape_website_details skipped: Ungueltige oder leere URL '{url}'.")
@@ -2411,6 +2339,7 @@ def alignment_demo(sheet):
sheet (gspread.Worksheet): Das Worksheet-Objekt zum Schreiben der Header.
"""
# Verwenden Sie logger, da das Logging jetzt konfiguriert ist
logger = logging.getLogger(__name__) # <<< DIESE ZEILE HINZUFÜGEN
# Stellen Sie sicher, dass COLUMN_MAP die hoechstbenuetigte Spalte AY (Index 50) enthaelt.
# Diese Funktion nimmt an, dass COLUMN_MAP komplett und korrekt ist.
# Die Listen unten muessen exakt die gleiche Laenge haben wie COLUMN_MAP.