This commit is contained in:
2025-04-23 05:36:57 +00:00
parent 3d337a39df
commit 9ae750d6bd

View File

@@ -359,18 +359,112 @@ def serp_wikipedia_lookup(company_name, website=None, min_score=0.4):
logging.error(f"Allgemeiner Fehler bei der SerpAPI Wikipedia Suche für '{company_name}': {e}")
return None # Bei unerwarteten Fehlern None zurückgeben
# Kann als eigenständige Funktion oder Methode in DataProcessor implementiert werden
# Annahme: COLUMN_MAP ist global definiert und enthält mindestens:
# "CRM Name" (B), "CRM Anzahl Mitarbeiter" (K), "CRM Umsatz" (J),
# "Wiki URL" (M), "ReEval Flag" (A), "Wiki Absatz" (N), ..., "Wiki Verif. Timestamp" (AX), "SerpAPI Wiki Search Timestamp" (AY)
# Annahme: serp_wikipedia_lookup und simple_normalize_url sind definiert und nutzen logging/retry
# Annahme: clean_text und normalize_company_name sind definiert
# Annahme: get_valid_numeric (oder ähnliche Logik zur numerischen Extraktion) ist verfügbar/implementiert.
# Die extract_numeric_value Funktion wird für die Extraktion aus Wikipedia-Daten verwendet.
# Wir benötigen hier eine ähnliche Logik, die aber 0 statt 'k.A.' zurückgibt.
# --- Hilfsfunktion zur sicheren numerischen Extraktion FÜR DIE FILTERLOGIK ---
def get_numeric_filter_value(value_str, is_umsatz=False):
"""
Extrahiert und normalisiert Zahlenwerte für die Filterlogik (Umsatz in Mio, Mitarbeiter int).
Gibt 0 zurück, wenn der Wert leer, k.A., nicht numerisch ist, oder 0 ergibt.
Beachtet Einheiten (Tsd, Mio, Mrd) für Umsatz.
"""
if value_str is None or pd.isna(value_str) or str(value_str).strip() == '':
return 0 # Leer oder k.A. -> 0
raw_value_str = str(value_str).strip()
if raw_value_str.lower() in ['k.a.', 'n/a', '-']:
return 0
try:
# Bereinigung ähnlich wie in clean_text und extract_numeric_value
processed_value = clean_text(raw_value_str) # Annahme: clean_text existiert
if processed_value == "k.A.": return 0
processed_value = re.sub(r'(?i)^\s*(ca\.?|circa|rund|etwa|über|unter|mehr als|weniger als|bis zu)\s+', '', processed_value)
processed_value = re.sub(r'[€$£¥]', '', processed_value).strip()
processed_value = re.split(r'\s*(-||bis)\s*', processed_value, 1)[0].strip()
processed_value_no_thousands = processed_value.replace('.', '').replace("'", "") # Entferne Punkte UND Apostrophe als Tausendertrenner
processed_value_final = processed_value_no_thousands.replace(',', '.') # Ersetze Komma durch Punkt für Dezimaltrennung
match = re.search(r'([\d.]+)', processed_value_final)
if not match:
# logging.debug(f"get_numeric_filter_value: Keine numerischen Zeichen gefunden in '{processed_value_final}'")
return 0 # Keine numerischen Zeichen gefunden
num_str = match.group(1)
if not num_str or num_str == '.': return 0 # Leerer oder nur Punkt String
num = float(num_str) # Konvertiere zum float
# --- Einheiten-Skalierung basierend auf ORIGINALSTRING ---
# Dies ist der kritische Teil für den Umsatz aus Spalte J, wenn er Einheiten enthalten kann
original_lower = raw_value_str.lower()
scale_factor = 1.0 # Skalierungsfaktor, um alles in die Basiseinheit zu bringen (z.B. 1 für Tsd, 1000 für Mio, 1000000 für Mrd)
is_scaled = False # Flag, ob eine Einheit gefunden wurde
if re.search(r'\bmrd\s*\b|\bmilliarden\s*\b|\bbillion\s*\b', original_lower):
scale_factor = 1000000.0 # 1 Mrd = 1000 Mio, also 1000*1000 = 1.000.000 Base units (assuming base is 1)
is_scaled = True
elif re.search(r'\bmio\s*\b|\bmillionen\s*\b|\bmill\.\s*\b', original_lower):
# Wenn der Wert bereits in Millionen angegeben ist (wie Spalte J),
# und die Einheit 'Mio' gefunden wird, muss keine Skalierung angewendet werden,
# da wir das Ergebnis in Millionen benötigen.
# ABER: Wir müssen prüfen, ob der *Schwellenwert* in Millionen ist.
# Der min_umsatz ist in Millionen (z.B. 200). Wir wollen den Wert in Spalte J
# auch in Millionen haben für den Vergleich.
# Wenn Original "6815", Einheit "Mio" -> num=6815, scale_factor=1, Ergebnis=6815 (Mio)
# Wenn Original "6.8 Mrd", Einheit "Mrd" -> num=6.8, scale_factor=1000 (Mio), Ergebnis=6800 (Mio)
# Das ist komplex. Vereinfachte Annahme: Wenn "Mrd" gefunden, multipliziere mit 1000 für Mio.
# Wenn "Tsd" gefunden, teile durch 1000 für Mio.
# Wenn "Mio" oder keine Einheit gefunden, nehme Zahl direkt als Mio.
if is_umsatz:
if re.search(r'\bmrd\s*\b|\bmilliarden\s*\b|\bbillion\s*\b', original_lower): # Wenn Mrd gefunden
num = num * 1000.0 # Skaliere zu Millionen
is_scaled = True
elif re.search(r'\btsd\s*\b|\btausend\s*\b', original_lower): # Wenn Tsd gefunden
num = num / 1000.0 # Skaliere zu Millionen
is_scaled = True
# Wenn "Mio" gefunden oder keine Einheit und is_umsatz=True,
# nehmen wir an, der Wert ist bereits in Millionen, keine weitere Skalierung nötig.
elif re.search(r'\btsd\s*\b|\btausend\s*\b', original_lower): # Wenn Tsd gefunden (für MA)
num = num * 1000.0 # Skaliere zu Tausendern
is_scaled = True
# Andere Einheiten (Mio, Mrd für MA) werden hier ignoriert, was bei MA OK ist.
# Das Ergebnis muss 0 oder positiv sein für die Filterlogik
result_num = num if num > 0 else 0 # Werte <= 0 zählen nicht
if is_umsatz:
# Rückgabe als Wert in Millionen
# Wenn keine Einheit gefunden wurde (und is_umsatz), gehen wir davon aus, dass es Millionen sind.
# Wenn eine Einheit (Tsd, Mrd) gefunden und skaliert wurde, ist es auch in Millionen.
return result_num # Der Wert sollte jetzt in Millionen sein, bereit für Vergleich mit min_umsatz
else: # Mitarbeiterzahl
# Rückgabe als ganze Zahl
return round(result_num) # Mitarbeiterzahl runden
except Exception as e:
logging.debug(f"Fehler in get_numeric_filter_value für Wert '{raw_value_str}': {e}")
return 0
# --- Ende Hilfsfunktion für Filter ---
# Nun die process_find_wiki_with_serp Funktion mit der neuen Logik
def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500, min_umsatz=200):
"""
Sucht fehlende Wikipedia-URLs (Spalte M = k.A.) für Unternehmen mit
(Umsatz CRM > min_umsatz ODER Mitarbeiter CRM > min_employees)
(Umsatz CRM > min_umsatz MIO € ODER Mitarbeiter CRM > min_employees)
über SerpAPI und trägt gefundene URLs in Spalte M ein. Setzt ReEval-Flag (A)
und löscht abhängige Wiki-Spalten (N-V, AN, AO, AP, AX).
Merkt sich in Spalte AY, wann die Suche durchgeführt wurde.
@@ -379,9 +473,9 @@ def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500
sheet_handler (GoogleSheetHandler): Initialisierte Instanz.
row_limit (int, optional): Maximale Anzahl zu prüfender Zeilen. Defaults to None.
min_employees (int, optional): Mindestanzahl Mitarbeiter (Spalte K) als Teilfilter. Defaults to 500.
min_umsatz (int, optional): Mindestumsatz (Spalte J) als Teilfilter. Defaults to 200.
min_umsatz (int, optional): Mindestumsatz in MIO € (Spalte J) als Teilfilter. Defaults to 200.
"""
logging.info(f"Starte Modus 'find_wiki_serp': Suche fehlende Wiki-URLs für Firmen mit (Umsatz CRM > {min_umsatz} ODER Mitarbeiter CRM > {min_employees})...")
logging.info(f"Starte Modus 'find_wiki_serp': Suche fehlende Wiki-URLs für Firmen mit (Umsatz CRM > {min_umsatz} MIO € ODER Mitarbeiter CRM > {min_employees})...")
if not sheet_handler.load_data(): return
all_data = sheet_handler.get_all_data_with_headers()
@@ -396,7 +490,7 @@ def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500
# Verwenden Sie hier das COLUMN_MAP robust
col_indices = {}
required_keys = [
"ReEval Flag", "CRM Anzahl Mitarbeiter", "CRM Umsatz", "Wiki URL", "CRM Name",
"ReEval Flag", "CRM Anzahl Mitarbeiter", "CRM Umsatz", "Wiki URL", "CRM Name", "CRM Website", # Website wird jetzt für SerpAPI Suche benötigt
"Wiki Absatz", "Wiki Branche", "Wiki Umsatz", "Wiki Mitarbeiter", "Wiki Kategorien",
"Chat Wiki Konsistenzprüfung", "Chat Begründung Wiki Inkonsistenz", "Chat Vorschlag Wiki Artikel",
"Begründung bei Abweichung", "Wikipedia Timestamp", "Timestamp letzte Prüfung",
@@ -426,48 +520,6 @@ def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500
now_timestamp_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# --- Hilfsfunktion zur sicheren numerischen Extraktion (adaptiert von prepare_data_for_modeling) ---
def safe_numeric_extract(value_str):
if value_str is None or pd.isna(value_str) or str(value_str).strip() == '': return 0 # Return 0, nicht NaN, für die Vergleichslogik
try:
# Annahme: clean_text existiert
processed_value = clean_text(str(value_str))
if processed_value == "k.A.": return 0
processed_value = re.sub(r'(?i)^\s*(ca\.?|circa|rund|etwa|über|unter|mehr als|weniger als|bis zu)\s+', '', processed_value)
processed_value = re.sub(r'[€$£¥]', '', processed_value).strip()
processed_value = re.split(r'\s*(-||bis)\s*', processed_value, 1)[0].strip()
# Entferne Punkte UND Apostrophe als Tausendertrenner, ersetze Komma durch Punkt
processed_value = processed_value.replace('.', '').replace("'", "").replace(',', '.')
match = re.search(r'([\d.]+)', processed_value)
if not match: return 0 # Keine numerischen Zeichen gefunden
num_str = match.group(1)
if not num_str or num_str == '.': return 0
num = float(num_str)
# Einheiten-Multiplikatoren (Mrd, Mio, Tsd) - Wichtig für Umsatz
multiplier = 1.0
original_lower = str(value_str).lower() # Nutze den Originalstring für Einheiten
if "mrd" in original_lower or "milliarden" in original_lower or "billion" in original_lower: multiplier = 1000000000.0
elif "mio" in original_lower or "millionen" in original_lower or "mill." in original_lower: multiplier = 1000000.0
elif "tsd" in original_lower or "tausend" in original_lower: multiplier = 1000.0
num = num * multiplier
# Für den Vergleich (Umsatz in Mio, Mitarbeiter int)
# Umsatz soll > 200 MIO sein, also num direkt mit 200,000,000 vergleichen
# Mitarbeiter > 500, num direkt mit 500 vergleichen
# Geben Sie den Rohwert nach Multiplikator zurück
return num if num > 0 else 0 # Nur positive Werte zählen
except Exception as e:
logging.debug(f"Fehler in safe_numeric_extract für Wert '{str(value_str)[:50]}...': {e}")
return 0
# --- Ende Hilfsfunktion ---
# Iteriere durch die Datenzeilen
for idx, row in enumerate(data_rows):
@@ -493,7 +545,7 @@ def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500
# Prüfe Wiki URL (M): Überspringe, wenn bereits gefüllt (nicht k.A. oder leer)
m_value = row[col_indices["Wiki URL"]]
if m_value and str(m_value).strip().lower() != "k.a.": # Jetzt auch 'leer' abfangen? Nein, '' ist schon abgedeckt
if m_value and str(m_value).strip().lower() != "k.a.":
skipped_m_filled_count += 1
continue
@@ -501,20 +553,21 @@ def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500
umsatz_val_str = row[col_indices["CRM Umsatz"]]
ma_val_str = row[col_indices["CRM Anzahl Mitarbeiter"]]
umsatz_val_num = safe_numeric_extract(umsatz_val_str)
ma_val_num = safe_numeric_extract(ma_val_str)
# Nutze die neue Hilfsfunktion, um die Werte für den Vergleich zu bekommen
umsatz_val_mio = get_numeric_filter_value(umsatz_val_str, is_umsatz=True) # Ergebnis in Mio €
ma_val_num = get_numeric_filter_value(ma_val_str, is_umsatz=False) # Ergebnis als Integer
# Filterlogik: Umsatz > min_umsatz ODER Mitarbeiter > min_employees
# Filterlogik: Umsatz > min_umsatz MIO € ODER Mitarbeiter > min_employees
# Wenn *nicht* (Umsatz > min_umsatz ODER Mitarbeiter > min_employees), dann überspringe
if not (umsatz_val_num > min_umsatz * 1000000 or ma_val_num > min_employees):
logging.debug(f"Zeile {row_num_in_sheet}: Übersprungen (Größe nicht ausreichend. Umsatz: {umsatz_val_num:.0f}, MA: {ma_val_num}).")
if not (umsatz_val_mio > min_umsatz or ma_val_num > min_employees):
logging.debug(f"Zeile {row_num_in_sheet}: Übersprungen (Größe nicht ausreichend. Umsatz (Mio): {umsatz_val_mio:.2f}, MA: {ma_val_num}). Schwellen: Umsatz > {min_umsatz} Mio, MA > {min_employees}.")
skipped_size_count += 1
continue
# --- Ende Größenprüfung ---
# Kandidat gefunden: M leer/k.A., AY leer, und Größe passt
company_name = row[col_indices["CRM Name"]] if len(row) > col_indices["CRM Name"] else ""
company_name = row[col_indices["CRM Name"]]
if not company_name or str(company_name).strip() == "":
logging.warning(f"Zeile {row_num_in_sheet}: Übersprungen, kein Firmenname für Suche vorhanden.")
# Setze AY Timestamp, damit wir nicht immer wieder versuchen
@@ -523,12 +576,13 @@ def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500
continue
# SerpAPI Suche
logging.info(f"Zeile {row_num_in_sheet}: Suche Wiki-URL für '{company_name}' (Umsatz: {umsatz_val_num:.0f}, MA: {ma_val_num})...")
logging.info(f"Zeile {row_num_in_sheet}: Suche Wiki-URL für '{company_name}' (Umsatz (Mio): {umsatz_val_mio:.2f}, MA: {ma_val_num})...")
processed_rows_count += 1 # Zähle VOR dem Call, dass ein Versuch gestartet wird
# Hole Website für SerpAPI Kontext
website_url = row[col_indices["CRM Website"]] if col_indices["CRM Website"] is not None and len(row) > col_indices["CRM Website"] else None
# Annahme: serp_wikipedia_lookup existiert und nutzt logging/retry
# website_url wird nicht direkt im SerpAPI Lookup verwendet, kann aber als Kontext hilfreich sein
website_url = row[COLUMN_MAP.get("CRM Website", -1)] if COLUMN_MAP.get("CRM Website", -1) != -1 and len(row) > COLUMN_MAP.get("CRM Website", -1) else None
wiki_url_found = serp_wikipedia_lookup(company_name, website=website_url)
# Updates vorbereiten
@@ -541,12 +595,14 @@ def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500
logging.info(f" -> URL gefunden: {wiki_url_found}. Bereite Update vor (Setze M, A; Lösche N-V, AN, AO, AP, AX).")
found_urls_count += 1
# Zusätzliche Updates für gefundene URL
# Indizes und Buchstaben für Updates holen
m_l = col_letters["Wiki URL"]
a_l = col_letters["ReEval Flag"]
# Spalten N-V leeren
n_l = col_letters["Wiki Absatz"]
v_l = col_letters["Begründung bei Abweichung"]
n_idx = col_indices["Wiki Absatz"]
v_idx = col_indices["Begründung bei Abweichung"]
n_l=sheet_handler._get_col_letter(n_idx+1)
v_l=sheet_handler._get_col_letter(v_idx+1)
# Timestamps AN, AO, AX, Version AP leeren
an_l = col_letters["Wikipedia Timestamp"]
ao_l = col_letters["Timestamp letzte Prüfung"]
@@ -557,9 +613,9 @@ def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500
all_sheet_updates.extend([
{'range': f'{m_l}{row_num_in_sheet}', 'values': [[wiki_url_found]]}, # URL setzen in M
{'range': f'{a_l}{row_num_in_sheet}', 'values': [['x']]}, # ReEval Flag setzen in A
# --- Spalten leeren, damit sie neu befüllt werden ---
# --- Spalten N-V leeren, damit sie neu befüllt werden ---
# Range N:V leeren
{'range': f'{n_l}{row_num_in_sheet}:{v_l}{row_num_in_sheet}', 'values': [[''] * (col_indices["Begründung bei Abweichung"] - col_indices["Wiki Absatz"] + 1)]},
{'range': f'{n_l}{row_num_in_sheet}:{v_l}{row_num_in_sheet}', 'values': [[''] * (v_idx - n_idx + 1)]},
{'range': f'{an_l}{row_num_in_sheet}', 'values': [['']]}, # AN leeren
{'range': f'{ao_l}{row_num_in_sheet}', 'values': [['']]}, # AO leeren
{'range': f'{ap_l}{row_num_in_sheet}', 'values': [['']]}, # AP leeren